Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I use std::shared pointer to pass a pointer?

Tags:

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.

like image 521
Michael Avatar asked Feb 23 '15 10:02

Michael


People also ask

Should you pass smart pointers by reference?

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.

Can you pass a shared pointer by reference?

In controlled circumstances you can pass the shared pointer by constant reference.

What is the point of a shared pointer?

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.

Is shared pointer slow?

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%.


2 Answers

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>; } 
like image 185
Richard Hodges Avatar answered Sep 21 '22 08:09

Richard Hodges


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.

like image 40
Chris Drew Avatar answered Sep 22 '22 08:09

Chris Drew