154

What is the best method to go about passing a shared_ptr of a derived type to a function that takes a shared_ptr of a base type?

I generally pass shared_ptrs by reference to avoid a needless copy:

int foo(const shared_ptr<bar>& ptr); 

but this doesn't work if I try to do something like

int foo(const shared_ptr<Base>& ptr); ... shared_ptr<Derived> bar = make_shared<Derived>(); foo(bar); 

I could use

foo(dynamic_pointer_cast<Base, Derived>(bar)); 

but this seems sub-optimal for two reasons:

  • A dynamic_cast seems a bit excessive for a simple derived-to-base cast.
  • As I understand it, dynamic_pointer_cast creates a copy (albeit a temporary one) of the pointer to pass to the function.

Is there a better solution?

Update for posterity:

It turned out to be an issue of a missing header file. Also, what I was trying to do here is considered an antipattern. Generally,

  • Functions that don't impact an object's lifetime (i.e. the object remains valid for the duration of the function) should take a plain reference or pointer, e.g. int foo(bar& b).

  • Functions that consume an object (i.e. are the final users of a given object) should take a unique_ptr by value, e.g. int foo(unique_ptr<bar> b). Callers should std::move the value into the function.

  • Functions that extend the lifetime of an object should take a shared_ptr by value, e.g. int foo(shared_ptr<bar> b). The usual advice to avoid circular references applies.

See Herb Sutter's Back to Basics talk for details.

13
  • 9
    Why do you want to pass a shared_ptr? Why no const-reference of bar? Commented Nov 15, 2012 at 18:08
  • 2
    Any dynamic cast is only needed for downcasting. Also, passing the derived pointer should work just fine. It'll create a new shared_ptr with the same refcount (and increase it) and a pointer to the base, which then binds to the const reference. Since you're already taking a reference, however, I don't see why you want to take a shared_ptr at all. Take a Base const& and call foo(*bar). Commented Nov 15, 2012 at 18:08
  • @Xeo: Passing the derived pointer (i.e. foo(bar)) doesn't work, at least in MSVC 2010. Commented Nov 15, 2012 at 18:11
  • 1
    What do you mean by "obviously doesn't work"? The code compiles and behaves correctly; are you asking how to avoid creating a temporary shared_ptr to pass to the function? I'm fairly sure there's no way to avoid that. Commented Nov 15, 2012 at 18:11
  • 1
    @Seth: I disagree. I think there is reason to pass a shared pointer by value, and there's very little reason to pass a shared pointer by reference (and all this without advocating unneeded copies). Reasoning here stackoverflow.com/questions/10826541/… Commented Nov 15, 2012 at 18:49

4 Answers 4

97

This will also happen if you've forgotten to specify public inheritance on the derived class, i.e. if like me you write this:

class Derived : Base { }; 

Instead of:

class Derived : public Base { }; 
Sign up to request clarification or add additional context in comments.

4 Comments

class is for template parameters; struct is for defining classes. (This is at most 45% a joke.)
This should definitely be considered the solution, there is no need for a cast as it is only missing public.
Saved me! Thank you very much. I'd like to know why casting only works with the public inheritance though, can you please elaborate on that?
Or if you, like me, have forgotten to specify the intended inheritance altogether... :-) I hope this helps someone!
64

Although Base and Derived are covariant and raw pointers to them will act accordingly, shared_ptr<Base> and shared_ptr<Derived> are not covariant. The dynamic_pointer_cast is the correct and simplest way to handle this problem.

(Edit: static_pointer_cast would be more appropriate because you're casting from derived to base, which is safe and doesn't require runtime checks. See comments below.)

However, if your foo() function doesn't wish to take part in extending the lifetime (or, rather, take part in the shared ownership of the object), then its best to accept a const Base& and dereference the shared_ptr when passing it to foo().

void foo(const Base& base); [...] shared_ptr<Derived> spDerived = getDerived(); foo(*spDerived); 

As an aside, because shared_ptr types cannot be covariant, the rules of implicit conversions across covariant return types does not apply when returning types of shared_ptr<T>.

10 Comments

They're not covariant, but shared_ptr<Derived> is implicitly convertible to shared_ptr<Base>, so the code should work with no casting shenanigans.
Um, shared_ptr<Ty> has a constructor that takes a shared_ptr<Other> and does the appropriate conversion if Ty* is implicitly convertible to Other*. And if a cast is needed, static_pointer_cast is the appropriate one here, not dynamic_pointer_cast.
True, but not with his reference parameter, as in the question. He would need to do a copy, regardless. But, if he's using refs to shared_ptr's to avoid the reference count, then there's really no good reason to be using a shared_ptr in the first place. It's best to use const Base& instead.
@PeteBecker See my comment to Mike about the conversion constructor. I honestly didn't know about static_pointer_cast, thanks.
@TanveerBadar Not sure. Perhaps this failed to compile in 2012? (specifically using Visual Studio 2010 or 2012). But you're absolutely right, OP's code should absolutely compile if the full definition of a /publicly derived/ class is visible to the compiler.
|
23

Also check that the #include of the header file containing the full declaration of the derived class is in your source file.

I had this problem. The std::shared<derived> would not cast to std::shared<base>. I had forward declared both classes so that I could hold pointers to them, but because I didn't have the #include the compiler could not see that one class was derived from the other.

2 Comments

Wow, I didn't expect it but this fixed it for me. I was being extra careful and only including header files where I needed them so some of them were only in source files and forward declaring them in headers as you said.
Stupid compiler is stupid. This was my issue. Thank you!
16

Sounds like you're trying too hard. shared_ptr is cheap to copy; that's one of its goals. Passing them around by reference doesn't really accomplish much. If you don't want sharing, pass the raw pointer.

That said, there are two ways to do this that I can think of off the top of my head:

foo(shared_ptr<Base>(bar)); foo(static_pointer_cast<Base>(bar)); 

16 Comments

No, they're not cheap to copy, they should be passed by reference whenever possible.
@SethCarnegie - did Herb profile your code to see whether passing by value was a bottleneck?
@SethCarnegie - that doesn't answer the question I asked. And, for what it's worth, I wrote the shared_ptr implementation that Microsoft ships.
@SethCarnegie - you've got the heuristic backwards. Hand optimizations should generally not be done unless you can show that they are needed.
It's only "premature" optimization if you need to work at it. I see no problem in adopting efficient idioms over inefficient ones, whether it makes a difference in a particular context or not.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.