6

Why can 'operator*' member function of std::unique_ptr be marked const (https://en.cppreference.com/w/cpp/memory/unique_ptr/operator*) while functions like 'front()', 'back()', 'operator[]' etc in std::vector not be marked const? Both are returning non-const references to the resources they are managing.

The core problem is I can't understand the 1st part i.e. why we're able to mark 'operator*' as const i.e. how can the return type be 'T&' when the '*this' pointer in that function would be a const pointer (due to the function being marked const)?

  • `std::vector` has const versions of `begin`, `front` and `operator[]` – NathanOliver Jun 07 '22 at 13:01
  • 2
    `std::unique_ptr::operator*` does not change the internal state of the unique pointer - so it should be marked `const` – Richard Critten Jun 07 '22 at 13:03
  • 2
    Because `operator*` cannot change the pointer. `const unique_ptr ptr` has the same semantics as `char* const ptr` variable - you can change the pointed-to content but cannot redirect the pointer somewhere else. – Quimby Jun 07 '22 at 13:03
  • `*this` is not a pointer, it is a `const unique_ptr`, which does not imply that `T` is const. That is, `const unique_ptr` is not "like" `const T*`, it is "like" `T* const`. (It helps to not think of the smart "pointers" as pointers at all, because they aren't. They are just regular classes.) – molbdnilo Jun 07 '22 at 13:17
  • 1
    This seems to be a new take on the many questions about ["const pointer" vs "pointer to const"](https://stackoverflow.com/questions/21476869/constant-pointer-vs-pointer-to-constant). – Drew Dormann Jun 07 '22 at 13:23
  • @NathanOliver the const versions of those functions have different return types (const_reference instead of reference). – not_that_guy123 Jun 07 '22 at 14:34
  • I should have framed the last part of the question better / correctly but I do understand the difference between a "const pointer" and a "pointer to const". And also realize the fact that for non-const functions, type of 'this' is 'T* const' (i.e. const pointer) while for const functions, it is 'const T* const' (i.e. const pointer to const data). However, I don't understand why we shouldn't then also be marking std::vector's front(), back(), operator[] functions as const, as they too aren't modifying the object in any way? – not_that_guy123 Jun 07 '22 at 14:49
  • @not_that_guy123 since `front()` can return a mutable reference, it is incorrect to think that `front()` does not provide the capability to modify the vector in any way. – Drew Dormann Jun 07 '22 at 15:17
  • @DrewDormann but that's also true for std::vector though, right? Both the things : (a) the data managed by the class doesn't reside inside the class, lies on the heap instead (b) we're returning a non-const reference to that data from the relevant function ('operator*' in case of std::unique_ptr and 'front / back' in case of std::vector) are true. So can't understand why there's a difference in the const-nes of these functions (const in case of std::unique_ptr and non-const in case of std::vector). – not_that_guy123 Jun 08 '22 at 02:50
  • The difference is like `T const *` vs `T *const` for unique_ptr. For vector, consider `T[]` vs `T const[]`. `unique_ptr` is supposed to resemble a raw pointer in some aspects, while vector is supposed to be like an array. If you can grasp pointer and array constness, then it is not hard to figure how `unique_ptr` and `vector` should behave. – Red.Wave Aug 03 '22 at 21:01

2 Answers2

6

The difference lies in the definition of a container.

A std::vector is considered a container. This means that the objects managed by a std::vector are considered parts of the vector.

The references returned from front(), back() and operator[] must be const if the vector is const. Modifying those objects will modify the vector.

A std::unique_ptr, however, is not a container. The object it manages is not considered to be part of the std::unique_ptr. This means that modifying the managed object is not considered modifying the pointer.

Therefore, using operator* on a pointer type will never change the pointer itself, and therefore the operation can always be considered const.


std::optional may provide some helpful context to this difference.

Despite having a syntax that resembles a pointer, it is effectively a container (with max_size of 1). Because it contains its managed object, its operator* must obey the same const rules that a container does.

Drew Dormann
  • 59,987
  • 13
  • 123
  • 180
  • I would argue that `std::unique_ptr` __is__ a container and what it contains is a pointer. The object pointed to however is not contained by the `std::unique_ptr` and `const` is only bound to the container. – Richard Critten Jun 07 '22 at 22:13
  • @Drew Dormann - does that mean that this difference in const-ness of certain functions between unique_ptr and vector are just due to the *semantics* associated with container classes in C++? – not_that_guy123 Jun 08 '22 at 03:14
  • 1
    @not_that_guy123 Yes. A `std::vector` models properties of an array, while a `std::unique_ptr` models properties of a pointer. Saying _"This array will not change"_ does not carry the same meaning as _"This pointer will not change"_, even if the pointer points to the array. – Drew Dormann Jun 08 '22 at 12:24
  • That makes perfect sense. Thanks! – not_that_guy123 Jun 09 '22 at 02:23
1

Drew Dormann's excellent answer above resolves the core confusion that I had. Adding a summary here (since it is too long to be posted as a comment) for complete clarity.

Doubt 1 : How are we able to use T& as the return type even though std::unique_ptr::operator*() is marked a const function?

Answer 1 : std::unique_ptr just stores the pointer to the data being managed (the data itself is not stored within the class, but rather on the heap). We're able to mark its operator*() function as const because we're returning a non-const reference to the actual data being stored & since that data itself is not stored inside the class, the 'const' qualifier just makes the pointer to the data const (i.e. we have a const pointer) but the data that this const pointer points to is still non-const and we can bind non-const references to it.

Doubt 2 : The same thing is true for std::vector (i.e. the data managed by it is not stored within the class, but on the heap instead) but then why we don't similarly mark functions like std::vector::front(), std::vector::back() etc as const?

Answer 2 : This is due to the semantics associated with container classes in C++. Quoting Drew from above - std::vector models properties of an array, while a std::unique_ptr models properties of a pointer. So we kind of act as if the data managed by std::vector resides inside the class. Note that std::optional is different from std::unique_ptr / std::vector as the data being managed is actually stored within the class.