11

I understand that std::unique_ptr is the way it is and probably won't be changed to break backwards compatibility but I was wondering if anyone has a good reason why the writers of the spec didn't overload the get method with a const variant that looks like

const T* get() const;

to follow the intent of the unique_ptr being const.

My best guess is that it is trying to mirror pointers and act like a T* const instead of a typical class. As a follow-up question, if I wanted to hold a pointer in a const-like fashion in a const instance of my class, should I be using something else other than std::unique_ptr to hold the data?

Update

In my case I want to protect myself from misusing the pointer in the class itself. I was writing a const move constructor MyClass(const MyClass&& other) and was copying the data from the new instance into other via std::copy. It took a long time to track down the bug because I had assumed the copy must be correct because of const protection. I'm trying to figure out what I could have done to protect myself from this outside of providing a const getter and using that within the class when doing the copy.

Dr. Gut
  • 2,053
  • 7
  • 26
quittle
  • 856
  • 2
  • 10
  • 19
  • Regarding the latter question, wouldn't `std::unique_ptr` do what you seek? Or did I misunderstand that part of the question. – WhozCraig May 18 '17 at 16:22
  • In my case my let's say I'm just wrapping some data like struct so all it is is a `char[]` and an `int` to represent the length. I want to pass it around and let consumers manipulate the bytes unless they have a const instance. Even if I provide const and non-const getters for the data, I want to ensure my internal methods don't mess with data on a const instance passed in to a method on my class – quittle May 18 '17 at 16:27
  • Related: http://stackoverflow.com/a/24428465/103167 – Ben Voigt May 18 '17 at 16:29
  • `std::unique_ptr::get()` is already const – Slava May 18 '17 at 18:34
  • 1
    I do not understand your update. It seems like an XY problem, or completely different question. Why on earth do you want a const move constructor? And what is "const protection"? – Oktalist May 19 '17 at 12:44
  • @Oktalist I'm sorry if I'm confusing, my update is trying to get to back to the problem I'm trying to solve, which is how do I prevent myself from having the same bug again. The const move constructor may be a bad design on my part but consider any member function that take a const instance by reference where I would extract data from it. By "const protection", I meant that I was safe from modifying anything controlled by the const reference so that when debugging I could skip looking at the const method as a culprit because the compiler would have stopped me from doing any modification. – quittle May 19 '17 at 14:48
  • 1
    Your const move constructor does not make any sense, if I understand you correctly, you just implemented a copy constructor. If a move constructor does not make sense in your class that's fine, just don't create one, no need to hack anything... – tr3w May 21 '17 at 18:52
  • @Slava He is saying that it returns a non-`const` pointer -- meaning from a `const` member function, you could potentially mutate a `std::unique_ptr` member since its `get` method is `const`, violating the expectation – bobobobo Nov 27 '21 at 14:05

5 Answers5

6

Smart pointers are pretending to be a raw pointer. If you have class member which is raw pointer and use it in const method that you can't update a pointer, but you can modify object which is pointed. Same behavior is desired for smart pointer. So std::unique_ptr::get is a const method, but doesn't force to return pointer to const object.

Note also that you can have a pointer to const object.

MyClass *pointerToObject
std::unique_ptr<MyClass> smartPointerToObject;

// but you can have also a case
const MyClass *pointerToConstObject
std::unique_ptr<const MyClass> smartPointerToConstObject;

In last case std::unique_ptr::get will return something you are expecting.


Based on comment below:

Just provide private methods:

InnerClass& GetField() { return *uniquePtrToInnerClass; }
const InnerClass& GetField() const { return *uniquePtrToInnerClass; }

And use it in your code and you will have const object of inner class in const method.

Marek R
  • 32,568
  • 6
  • 55
  • 140
  • I want the contents to be mutable event the instance is mutable and non-mutable when it isn't so I can't know when instantiating the class or storing the data whether or not to store it as a const or non-const version. – quittle May 19 '17 at 15:00
  • `std::unique_ptr` doesn't fulfill your needs? Also you write such complex sentence in comment that I'm unable to understand it. – Marek R May 19 '17 at 15:12
  • it doesn't fulfill by needs. Consider there's `Outer` and `Inner` and `Outer` contains a `unique_ptr`. Outer needs to manipulate the Inner instance sometimes but it would go against functionality of Outer to manipulate Inner within a const method. – quittle May 19 '17 at 15:27
5

There's no point to giving read-only access to an object via its unique_ptr. You only pass unique_ptr around when you are transferring ownership, for access to the object without an ownership transfer, call up.get() and pass a const T* to the function that should only read (or if the pointer is never nullptr, it's also reasonable to evaluate *(up.get()) and pass a const T&).

As a bonus, this allows you to use that function with objects stored on the stack, embedded inside another object, or managed with a smart pointer other than unique_ptr.

There's a good discussion of all the unique_ptr parameter passing cases (in/out, const/non-const, etc) here:

Community
  • 1
  • 1
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 1
    The problem I'm trying to solve is not passing the unique_ptr around, it's protecting me from myself within the class when accessing a const instance. I could add a const get for every smart pointer in every class to always protect myself, but that's a lot of extra code. – quittle May 19 '17 at 14:52
  • I guess what he's getting at is you can mutate the internals of a `unique_ptr` member from a `const` member function, even though that member function is marked `const`. You couldn't do that if it weren't for the `unique_ptr` (eg if you stored a raw pointer instead of the `unique_ptr`) – bobobobo Nov 27 '21 at 02:02
  • @bobobobo: If you had a raw pointer, you could still mutate the pointed-to object. The pointer type would be `T* const` not `const T*`. You couldn't replace the pointer (but `get()` is not proposing to replace the pointer) – Ben Voigt Nov 28 '21 at 03:13
5

For the same reason a T*const when dereferenced is a T&, not a T const&.

Constness of pointer is distinct from pointness of pointed-to.

get is const, it does not modify the state of unique_ptr.

Its constness does not impact the constness of the contents.

There is the idea of smart pointers that propogate constness, but unique_ptr is not that beast.

std::experimental::propogate_const wraps a pointer-like object and makes const travel through it.

It, or something like it, may solve your problem.

Note that I find half the time when I try to have const propogate like this, I discover I was wrong. But this may not be the case here.

In general, the proper way to handle the guts of a T*const in a const manner is to pass a T const& (or the nullable variant T const*).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 2
    `std::experimental::propagate_const>` ? – Daniel Schepler May 18 '17 at 18:38
  • 1
    You're just repeating the question, which said "My best guess is that it is trying to mirror pointers and act like a `T* const` instead of a typical class. " – Ben Voigt May 18 '17 at 18:40
  • propogate_const looks somewhat promising but in my case using it would result in the strange pattern of always wrapping calls to unique_ptrs in this call when accessing them in const methods. That is a bit closer to what I want but still rather verbose to use across every class that uses a unique_ptr – quittle May 19 '17 at 14:57
  • 2
    @quittle This "deep const" is not supposed to be an "everywhere thing"? You can write an alias in any case -- `template using flat_ptr = std::experimental::propotate_const>`. This class only makes sense to use within classes that contain a logically const pointer; other uses of `unique_ptr` shouldn't use it. – Yakk - Adam Nevraumont May 19 '17 at 15:28
1

I think this concern is valid, there should be 2 versions for each de-referencing functions,

e.g.
    const T* get() const;
    T* get();
    enter code here

I know that purpose of providing "T* get() const" is to ease replace existing raw pointer usages.

But since uniq ptr denotes ownership, it is incorrect that some one being able to modify some thing OWNED by the object via a immutable(const) reference [assuming modifying something fully owned by a object is same as modifying the object itself - which is true if this was an object instead of a ptr].

May be best option would be std to provide another version of Uniq ptr which holds to above idiom (only other option may be to derive a new class from uniq ptr and provide 2 versions for de-referencing )

aKumara
  • 395
  • 1
  • 12
0

Because as far as the unique_ptr is concerned, getting the internal raw pointer reference is a const operation. Calling .get() and retrieving the internal raw pointer of a std::unique_ptr does not change the internal state of the std::unique_ptr object itself. So it seems the library designers elected to mark it const without attention to what could happen to the underlying object if they just return a straight non-const reference to it.

In fact, if you have a std::unique_ptr inside an object, and you call a const member function of that object, you can still call non-const member functions on the internal std::unique_ptr inside that object. For example:

struct A {
    void Const() const { }
    void nonConst() { }
};

struct B {
    std::unique_ptr<A> a;
    void go() const {
        a->nonConst(); // OK
    }
};

Although you cannot perform non-const operations on the internal state variables of an object from one of its const member function, there is no rule that says you cannot perform non-const operations on other objects.

What you may be expecting is the constness promise to carry over from the unique_ptr to also apply to access to what it internally points to, so you'd expect unique_ptr to be written something like this:

template <typename T>
class cunique_ptr {
    T* ptr;
public:
    cunique_ptr() {
        ptr = new T;
    }
    ~cunique_ptr() {
        delete ptr;
    }

    // You can only get a non-const pointer to the internals from a non-const object
    T* get() { return ptr; }

    // The const member function carries over the const promise to access to its internals
    const T* get() const { return ptr; }
};

void test() {
    cunique_ptr<A> a;
    a.get()->nonConst();

    const cunique_ptr<A> ca;
    //ca.get()->nonConst(); //X fails: cannot call non-const member functions from const object
    ca.get()->Const();
}

However, it seems the library designers elected against that type of protection and let the const promise be kind of shallow as it were.

bobobobo
  • 64,917
  • 62
  • 258
  • 363