Suppose I have an object which is managed by an std::unique_ptr
. Other parts of my code need to access this object. What is the right solution to pass the pointer? Should I just pass the plain pointer by std::unique_ptr::get
or should I use and pass an std::shared_ptr
instead of the std::unique_ptr
at all?
I have some preference for the std::unique_ptr
because the owner of that pointer is actually responsible for cleanup. If I use a shared pointer, there's a chance that the object will remain alive due to a shared pointer even when it should actually be destroyed.
EDIT: Unfortunately, I forgot to mention that the pointer will not just be a an argument to a function call, but it will be stored in other objects to build up a network structure of objects. I do not prefer the shared pointer because then it's no longer clear, who actually owns the object.
It's ok to pass a smart pointer by reference, except if it's to a constructor. In a constructor, it's possible to store a reference to the original object, which violates the contract of the smart pointers.
In controlled circumstances you can pass the shared pointer by constant reference.
A shared_ptr can share ownership of an object while storing a pointer to another object. This feature can be used to point to member objects while owning the object they belong to. The stored pointer is the one accessed by get(), the dereference and the comparison operators.
Admittedly, the std::shared_ptr is about two times slower than new and delete. Even std::make_shared has a performance overhead of about 10%.
If the ownership of the managed object is not being transferred (and because it's a unique_ptr
, ownership cannot be shared) then it's more correct to separate the logic in the called function from the concept of ownership. We do this by calling by reference.
This is a convoluted way of saying:
Given:
std::unique_ptr<Thing> thing_ptr;
to change the Thing:
// declaration void doSomethingWith(Thing& thing); // called like this doSomethingWith(*thing_ptr);
to use the Thing without modifying it.
// declaration void doSomethingWith(const Thing& thing); // called like this doSomethingWith(*thing_ptr);
The only time you'd want to mention the unique_ptr
in the function signature would be if you were transferring ownership:
// declaration void takeMyThing(std::unique_ptr<Thing> p); // call site takeMyThing(std::move(thing_ptr));
You never need to do this:
void useMyThing(const std::unique_ptr<Thing>& p);
The reason that this would be a bad idea is that if confuses the logic of useMyThing with the concept of ownership, thus narrowing the scope for re-use.
Consider:
useMyThing(const Thing& thing); Thing x; std::unique_ptr<Thing> thing_ptr = makeAThing(); useMyThing(x); useMyThing(*thing_ptr);
Update:
Noting the update to the question - storing (non-owning) references to this object.
One way to do this is indeed to store a pointer. However, pointers suffer from the possibility of a logic error in that they can legally be null. Another problem with pointers is that they do not play nicely with std:: algorithms
and containers - requiring custom compare functions and the like.
There is a std::-compliant
way to do this - the std::reference_wrapper<>
So rather than this:
std::vector<Thing*> my_thing_ptrs;
do this:
std::vector<std::reference_wrapper<Thing>> my_thing_refs;
Since std::reference_wrapper<T>
defines an operator T&
, you can use the reference_wrapped
object in any expression that would expect a T
.
for example:
std::unique_ptr<Thing> t1 = make_thing(); std::unique_ptr<Thing> t2 = make_thing(); std::unique_ptr<Thing> t3 = make_thing(); std::vector<std::reference_wrapper<const Thing>> thing_cache; store_thing(*t1); store_thing(*t2); store_thing(*t3); int total = 0; for(const auto& t : thing_cache) { total += value_of_thing(t); }
where:
void store_thing(const Thing& t) { thing_cache.push_back(std::cref(t)); } int value_of_thing(const Thing& t) { return <some calculation on t>; }
Typically you would just pass a reference or plain pointer to other parts of the code that wish to observe the object.
Pass by reference:
void func(const Foo& foo); std::unique_ptr<Foo> ptr; // allocate ptr... if(ptr) func(*ptr);
Pass by raw pointer:
void func(const Foo* foo); std::unique_ptr<Foo> ptr; // allocate ptr... func(ptr.get());
The choice will depend on the need to pass a null pointer.
It is your responsibility to ensure by-design that observers do not use the pointer or reference after the unique_ptr
has been destroyed. If you can't guarantee that then you must use a shared_ptr
instead of a unique_ptr
. Observers can hold a weak_ptr
to indicate that they do not have ownership.
EDIT: Even if observers wish to hold on to the pointer or reference that is OK but it does make it more difficult to ensure it will not be used after the unique_ptr
has been destroyed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With