Passing Shared Pointers as Arguments

Passing shared pointers as arguments

I want to pass a shared pointer to a function. Can you help me with that?

Sure, I can help with you that. I assume you have some understanding of ownership semantics in C++. Is that true?

Yeah, I'm reasonably comfortable with the subject.

Good.

Ok, I can only think of two reasons to take a shared_ptr argument:

  1. The function wants to share ownership of the object;
  2. The function does some operation that works specifically on shared_ptrs.

Which one are you interested in?

I'm looking for a general answer, so I'm actually interested in both. I'm curious about what you mean in case #2, though.

Examples of such functions include std::static_pointer_cast, custom comparators, or predicates. For example, if you need to find all unique shared_ptr from a vector, you need such a predicate.

Ah, when the function actually needs to manipulate the smart pointer itself.

Exactly.

In that case, I think we should pass by reference.

Yes. And if it doesn't change the pointer, you want to pass by const reference. There's no need to copy since you don't need to share ownership. That's the other scenario.

Ok, got it. Let's talk about the other scenario.

The one where you share the ownership? Ok. How do you share ownership with shared_ptr?

By copying it.

Then the function will need to make a copy of a shared_ptr, correct?

Obviously. So I pass it by a reference to const and copy to a local variable?

No, that's a pessimization. If it is passed by reference, the function will have no choice but to make the copy manually. If it is passed by value the compiler will pick the best choice between a copy and a move and perform it automatically. So, pass by value.

Good point. I must remember that "Want Speed? Pass by Value." article more often.

Wait, what if the function stores the shared_ptr in a member variable, for example? Won't that make a redundant copy?

The function can simply move the shared_ptr argument into its storage. Moving a shared_ptr is cheap because it doesn't change any reference counts.

Ah, good idea.

But I'm thinking of a third scenario: what if you don't want to manipulate the shared_ptr, nor to share ownership?

In that case, shared_ptr is completely irrelevant to the function. If you want to manipulate the pointee, take a pointee, and let the callers pick what ownership semantics they want.

And should I take the pointee by reference or by value?

The usual rules apply. Smart pointers don't change anything.

Pass by value if I'm going to copy, pass by reference if I want to avoid a copy.

Right.

Hmm. I think you forgot yet another scenario. What if I want to share ownership, but only depending on a certain condition?

Ah, an interesting edge case. I don't expect that to happen often. But when it happens you can either pass by value and ignore the copy if you don't need it, or pass by reference and make the copy if you need it.

I risk one redundant copy in the first option, and lose a potential move in the second. Can't I eat the cake and have it too?

If you're in a situation where that really matters, you can provide two overloads, one taking a const lvalue reference, and another taking an rvalue reference. One copies, the other moves. A perfect-forwarding function template is another option.

I think that covers all the possible scenarios. Thank you very much.

Passing a shared pointer to a function argument - what is the right way to assign it to a local variable

The rule of thumb for smart pointer function arguments is: if there is no notion of ownership, don't pass the smart pointer, pass the pointee

  1. as a reference, if it can't be null (preferable, see comments)
  2. as a pointer, if it can be null (don't forget to check, though)

In case 1, any call to delete on the argument will fail to compile, which might be desirable. Further const qualification of the argument is orthogonal. In your example:

void SetLogger(const A& log);

called like this

auto a = std::make_shared<A>(/* ... */);

SetLogger(*a);

So how should create a local shared_ptr in the function stating the restriction above?

If you need to copy the argument, there is indeed a notion of ownership. In that case, change your function signature to

void SetLogger(std::shared_ptr<A> log);

This will, however, increment the reference count, but if the object is managed by a std::shared_ptr, there is no sane solution to retain a copy without touching the reference count (for a good reason, this it what the class is about).

Whether to pass shared pointer or raw pointer to a function

I would suggest the following approach to looking at code something like this:

Sample Image

There are some other options like weak_ptr, but for this it is probably not worth looking at.

So for your example, we can see that ThirdParty_DoStuff does not take ownership, so we won't either, so you can choose between a reference and a pointer depending on if the argument is mandatory or not respectively.

std::shared_ptr - Best practice for passing shared pointer as parameter

You should pass around shared pointers exactly as you pass around other objects. If you need to store a copy (of the shared pointer, not the pointed at object), pass by value, and move to its destination.

ServiceA(std::shared_ptr<ServiceB> serviceB)
: _serviceB(std::move(serviceB)) {}

Alternatively, if you don't mind writing two constructors, you can save a tiny bit of performance (one call to the shared pointer's the move constructor) by writing one which takes a const reference and copies it, and one which takes an r-value reference, and moves it.

ServiceA(std::shared_ptr<ServiceB> const& serviceB)
: _serviceB(serviceB) {}

ServiceA(std::shared_ptr<ServiceB> && serviceB)
: _serviceB(std::move(serviceB) {}

Passing a shared pointer by reference or by value as parameter to a class

Pass it by value then move it into the member:

class Foo {
public:
Foo(std::shared_ptr<Boo> boo)
: m_Boo(std::move(boo)) {}
private:
std::shared_ptr<Boo> m_Boo;
};

This will be the most efficient in all cases - if the caller has a rvalue-reference then there wont be a single add-ref, if the caller has a value there'll be a single add-ref.

If you pass by const& you force an add-ref even in cases where its unnecessary. If you pass by value and then set without a std::move you may get 2 add-refs.

Edit: This is a good pattern to use if you've got a class where a move is significantly cheaper than a copy, and you have a function call which will always copy the instance passed in - as in this case. You force the copy to happen at the function call boundary by taking it by value, and then if the caller has a rvalue reference the copy need never happen at all - it will be moved instead.

What is the best practice when passing a shared pointer to a non-owning function?

I would suggest to pass them by const std::shared_ptr<T>&. This has two benefits:

  1. No overhead incurred with reference counts (the reference count is not increased by default)
  2. The function is free to take a copy of the shared pointer if necessary

I use this a lot in situations where they may (or may not) be a thread handoff. For instance:

struct Foo { void bar() {} };

class BaseImpl
{
public:
void CallBar(const std::shared_ptr<Foo>& f);
{
do_call_bar(f);
}

private:
virtual void do_call_bar(const std::shared_ptr<Foo>& f) = 0;
};

class DerivedDirectCall : public BaseImpl
{
virtual void do_call_bar(const std::shared_ptr<Foo>& f) override
{
f->bar();
}
};

class DerivedAsyncCall : public BaseImpl
{
virtual void do_call_bar(const std::shared_ptr<Foo>& f) override
{
// assume invoke passes this off
// to be done asynchronously
invoke([f] () { f->bar(); });
}
};

Depending on whether the implementation is DerivedDirectCall or DerivedAsyncCall the shared pointer will only be copied if necessary.

Also, a comment above linked this article on smart pointers. I want to be clear in that my answer is specifically about passing the smart pointer itself. If you know the lifetime won't necessarily be extended, it's better to pass the pointed to object by reference (or raw pointer, but only it's necessary to allow for nullptr parameters).

What are the reasons for passing a pointers to a shared_ptr to a function?

std::shared_ptr<…>* is used in Arrow when a function is returning an object as a shared_ptr while at the same time the function may fail with one of arrow::Status codes.

Apache Arrow C++ adheres to the Google C++ style guide. One of the aspects is to not use exceptions. Furthermore, normally output will be done with a normal return statement but in the cases where we also need to return a Status, we use the alternative approach of returning it via a non-const pointer.

For inputs where Arrow takes no ownership of the passed parameter, instead of std::shared_ptr<T>, functions take const T&. Shared pointers only appear in function signature if ownership is shared afterwards or when the parameter is an output parameter.

Should we pass a shared_ptr by reference or by value?

This question has been discussed and answered by Scott, Andrei and Herb during Ask Us Anything session at C++ and Beyond 2011. Watch from 4:34 on shared_ptr performance and correctness.

Shortly, there is no reason to pass by value, unless the goal is to share ownership of an object (eg. between different data structures, or between different threads).

Unless you can move-optimise it as explained by Scott Meyers in the talk video linked above, but that is related to actual version of C++ you can use.

A major update to this discussion has happened during GoingNative 2012 conference's Interactive Panel: Ask Us Anything! which is worth watching, especially from 22:50.

Should I pass a shared_ptr by reference?

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.



Related Topics



Leave a reply



Submit