119

What are the best practices for passing a shared_ptr?

Currently I pass shared_ptr function arguments like so:

void function1( shared_ptr<TYPE>& value );
mindless.panda
  • 3,806
  • 3
  • 32
  • 56
Ben Crowhurst
  • 7,042
  • 5
  • 40
  • 72
  • 3
    See my answer to [shared_ptr by reference or by value?](http://stackoverflow.com/a/8741626/151641) where I reference definitive answer given by Scott Meyers, Herb Sutter, and Andrei Alexandrescu. – mloskot Jan 05 '12 at 11:22

3 Answers3

133

In controlled circumstances you can pass the shared pointer by constant reference. Be sure that nobody is concurrently deleting the object, though this shouldn't be too hard if you're careful about to whom you give references.

In general, you should pass the shared pointer as a straight copy. This gives it its intended semantics: Every scope that contains a copy of the shared pointer keeps the object alive by virtue of its "share" in the ownership.

The only reason not to always pass by value is that copying a shared pointer comes at a certain price on account of the atomic reference count update; however, this might not be a major concern.


Optional digression:

Since the main question has been answered, perhaps it is instructive to consider a few ways in which you should never use a shared pointer. Here is a little thought experiment. Let us define a shared pointer type SF = std::shared_ptr<Foo>. In order to consider references, rather than passing function arguments let us look at the type RSF = std::reference_wrapper<T>. That is, if we have a shared pointer SF p(std::make_shared<Foo>());, then we can make a reference wrapper with value semantics via RSF w = std::ref(p);. So much for the setup.

Now, everybody knows that containers of pointers are minefield. So std::vector<Foo*> will be a nightmare to maintain, and any number of bugs arise from improper lifetime management. What's worse conceptually is that it is never clear who owns the objects whose pointers the container stores. The pointers could even be a mix of pointers to dynamic objects, automatic objects, and garbage. Nobody can tell. So the standard solution is to use std::vector<SF> instead. This is The Right Way to use the shared pointer. On the other hand, what you must never use is std::vector<RSF> -- this is an unmanageable monster that is actually very similar to the original vector of naked pointers! For example, it's not clear whether the object to which you hold a reference is still alive. Taking a reference of the shared pointer has defeated its entire purpose.

For a second example, suppose we have a shared pointer SF p as before. Now we have a function int foo(SF) that we want to run concurrently. The usual std::thread(foo, p) works just fine, since the thread constructor makes a copy of its arguments. However, had we said std::thread(foo, std::ref(p)), we'd be in all sorts of trouble: The shared pointer in the calling scope could expire and destroy the object, and you would be left with a dangling reference and an invalid pointer!

I hope these two admittedly fairly contrived examples shed a bit of light on when you really want your shared pointers to be passed around by copy. In a well-designed program, it should always be clear who is responsible for which resources, and when used right, the shared pointer is a great tool for the job.

Kerrek SB
  • 428,875
  • 83
  • 813
  • 1,025
  • Could you elaborate on what you mean by "concurrently deleting the object"? Are you implying that there is any danger beyond the "usual" dangers that arise whenever you pass anything by reference? – wolfgang Dec 05 '11 at 13:04
  • 1
    @wolfgang: I just posted a little digression that might explain that --- but imagine that several concurrent contexts have a reference to the same SP; then one could `reset()` it and the other would end up not just with a modified reference to the SP, but *also* with a possibly invalid pointer to the resource. – Kerrek SB Dec 05 '11 at 13:23
  • Right, that's what I meant by the "usual" dangers. But then, if a concurrent thread modifies the SP while I'm copying it, I am in trouble as well (though I might get away with that in some implementations of std::SP). It's the same if I was passing an std::string object (either const std::string& or by value). – wolfgang Dec 05 '11 at 13:36
  • Summary for beginners (and other non-wizards)... if you know exactly what happens when you pass a const std::string&, feel free to do the same for std::shared_ptr in order to get some performance gain ranging from irrelevant to substantial, depending on the exact situation. If const whatever& is black magic to you, stay away from it, it's not worth the risk. – wolfgang Dec 05 '11 at 13:39
  • 3
    @wolfgang: With a bit less magic: If you don't trust the callee to *not* store a reference to your pointer, then always pass a value, since that says, "here, you take responsibility". Passing by const-reference is like "hold that for me for a minute please", and if you think they might drop it, then don't. – Kerrek SB Dec 05 '11 at 13:51
  • My head is spinning! So in multi-threaded code, passing a shared_ptr by value is preferred because of the atomic reference increment/decrement. But in single-threaded code, where the callee isn't supposed to share ownership, passing by value sends the wrong message. And passing by const reference in single-threaded code avoids the increment/decrement cost but, in multi-threaded code, there's a risk a shared_ptr reset can invalidate our const reference at any moment. Wouldn't it be better then to pass a const ptr so we can test with if( ptr )? I guess we'd have to make its every use atomic too? – pbyhistorian Nov 10 '16 at 07:53
  • 1
    @pbyhistorian: Generally, you should have a clear mental model of your problem and your design, and then it's not all that scary. First off, the times you actually want a shared pointer (rather than a unique pointer) are going to be very small. Once you do have a genuine need to share ownership, you will either pay the cost for the service you need, or use direct references locally when you already have a share and can guarantee the lifetime. And you'll still need to think about synchronizing access to the managed value itself if you are in a multithreaded setting anyway. – Kerrek SB Nov 10 '16 at 15:21
  • Performance is not the only reason for passing a shared pointer by reference. If you pass a shared pointer by value, then whenever you step into the function in the debugger, you will end up in the copy constructor of the shared pointer first. This is annoying. To avoid it, I tend to favor passing shared pointers by reference wherever possible. – Kees-Jan Jan 02 '19 at 08:27
39

That depends on what you want. Should the callee share ownership of the object? Then it needs its own copy of the shared_ptr. So pass it by value.

If a function simply needs to access an object owned by the caller, go ahead and pass by (const) reference, to avoid the overhead of copying the shared_ptr.

The best practice in C++ is always to have clearly defined ownership semantics for your objects. There is no universal "always do this" to replace actual thought.

If you always pass shared pointers by value, it gets costly (because they're a lot more expensive to copy than a raw pointer). If you never do it, then there's no point in using a shared pointer in the first place.

Copy the shared pointer when a new function or object needs to share ownership of the pointee.

jalf
  • 229,000
  • 47
  • 328
  • 537
  • "If you never do it, then there's no point in using a shared pointer in the first place." - you think of “never copy” but refer to “pass by value”. Some one may need to only store shared_ptr and always pass it by const reference. Passing by reference is faster because it do not increment/decrement reference counter. – Arpegius Dec 05 '11 at 15:06
  • @lionbest: no, I think of pass by value. If you never pass by value, there's no point in a shared_ptr, because then only one client will hold an actual shared_ptr, and then it isn't *shared*. – jalf Dec 05 '11 at 16:12
  • 4
    You can make a copy `void store( shared_ptr const& sharedRef ) { this->mySharedRef=shredRef;}`. If shredRef will be pass by value it will increment counter twice. Of course you can do `void store( shared_ptr sharedRef ) { shredRef.swap(this->mySharedRef);}` to do the same. – Arpegius Dec 05 '11 at 17:54
10

Pass it by const reference if you pass by reference. That makes it clear that you're passing by ref for performance reasons. Also, use make_shared when you can since it saves an indirection so gives a perf boost.

Kate Gregory
  • 18,565
  • 8
  • 53
  • 85
  • 1
    `make_shared` is always a good thing, but note that it only plays a role when creating the first shared pointer. – Kerrek SB Dec 05 '11 at 13:03
  • @Kerrek SB: Not always true. For some typical implementations, `std::make_shared` would bloat binary size of the executable image a bit due to RTTI stuff, whether **the allocation would actually benefit the runtime performance or not**. This should not be a problem for desktop/server platforms, but may be a problem for embedded environment with extremely limited resources. – FrankHB Sep 19 '15 at 12:16
  • @FrankHB: How would that "RTTI stuff" not also exist for the other implementations? You'll always increase the binary size for every new type you use, so at best you could try to avoid using two different implementations for the same type. – Kerrek SB Sep 19 '15 at 14:28
  • @KerrekSB: The binary size would bloat because the library use `typeid` for internal implementation. However, I cannot use it directly, because the type is not exposed. For example, libstdc++ uses `typeid(_Sp_make_shared_tag)` to get deleter for `shared_ptr` allocated by `allocate_shared`/`make_shared` when RTTI is enabled. I have no choice to disable it without modifying the source in headers, if I still need RTTI elsewhere. Actually the resulted `type_info` is a placeholder, no runtime type information is really needed. So something like `boost::typeindex::type_id` should make things better. – FrankHB Sep 21 '15 at 00:52