Passing shared_ptr<Derived> as shared_ptr<Base>
Solution 1
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>
.
Solution 2
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
{
};
Solution 3
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.
Solution 4
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));
Matt Kline
Embedded software engineer, typography & design enthusiast. BS in Computer Engineering and Computer Sciences from the University of Wisconsin-Madison Interests: Rust, hardware-software interface, performance-critical design, technical writing & presentation
Updated on April 14, 2021Comments
-
Matt Kline about 3 years
What is the best method to go about passing a
shared_ptr
of a derived type to a function that takes ashared_ptr
of a base type?I generally pass
shared_ptr
s 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 shouldstd::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.
- A
-
Mike Seymour over 11 yearsThey're not covariant, but
shared_ptr<Derived>
is implicitly convertible toshared_ptr<Base>
, so the code should work with no casting shenanigans. -
Pete Becker over 11 yearsUm,
shared_ptr<Ty>
has a constructor that takes ashared_ptr<Other>
and does the appropriate conversion ifTy*
is implicitly convertible toOther*
. And if a cast is needed,static_pointer_cast
is the appropriate one here, notdynamic_pointer_cast
. -
Bret Kuhns over 11 yearsTrue, 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 ashared_ptr
in the first place. It's best to useconst Base&
instead. -
Bret Kuhns over 11 years@PeteBecker See my comment to Mike about the conversion constructor. I honestly didn't know about
static_pointer_cast
, thanks. -
Seth Carnegie over 11 yearsNo, they're not cheap to copy, they should be passed by reference whenever possible.
-
Pete Becker over 11 years@SethCarnegie - do you have measurements to establish that its a bottleneck in your code? It's just an atomic integer increment.
-
Seth Carnegie over 11 yearsI'm just saying what Herb Sutter says. Watch channel9.msdn.com/Shows/Going+Deep/… and go to 4:34
-
Pete Becker over 11 years@SethCarnegie - did Herb profile your code to see whether passing by value was a bottleneck?
-
Seth Carnegie over 11 yearsI trust Sutter more than you. You didn't profile my code either to determine it's not a bottleneck, so it's you vs. him, and he wins.
-
Pete Becker over 11 years@SethCarnegie - that doesn't answer the question I asked. And, for what it's worth, I wrote the
shared_ptr
implementation that Microsoft ships. -
Matt Kline over 11 years@PeteBecker: It has a definite cost, and should be avoided when possible. See this thread
-
Pete Becker over 11 years@slavik262 - no, it sometimes should be avoided. Like any hand optimization, it should be done if you have evidence that it's a bottleneck in your program. If I pass half a dozen
shared_ptr
objects in two dozen places, there's no reason at all to complicate things by passing them by reference. -
Pete Becker over 11 years@SethCarnegie - you've got the heuristic backwards. Hand optimizations should generally not be done unless you can show that they are needed.
-
Seth Carnegie over 11 years@PeteBecker (regarding you writing the MSVC implementation of
shared_ptr
) then I find it amazing that you don't concede to Herb Sutter knowing what you know. I did answer your question, just not with the answer you wanted. When it's something as trivial as passing by reference, it doesn't complicate things. Not passing by reference is premature pessimisation. Why pass stuff by value for the heck of it? Seriously, if someone like Sutter recommends something, you don't really change your mind unless someone else you respect more recommends the opposite, and that's not happening here. -
Mark Ransom over 11 yearsIt'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.
-
Pete Becker over 11 years@SethCarnegie - if "it doesn't complicate things", why did the original poster have to ask how to do it?
-
Jean-Michaël Celerier over 7 years@PeteBecker "Hand optimizations should generally not be done unless you can show that they are needed." writing
const Foo&
instead ofstd::shared_ptr<Foo>
is certainly an optimization for my both hands. Seriously, if it takes less time to write, is more clear, AND is faster, it isn't an optimization, it is mandatory. -
Chris O over 7 years@MarkRansom In this case, you do need to work at it, you have to prove that
foo
won't call anything that will implicitly causeptr
to be deleted, thus leading to AV, if the rest offoo
attempts to use the smart-pointer-as-reference. (This was actually a problem in one codebase I've maintained, though to be fair, things might not have been "as tight" as they should have been). -
jigglypuff over 5 yearsWow, 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.
-
Tom Mettam about 5 yearsThe performance degradation doesn't come at assignment time, but when the refcount is decremented. Please see this answer (not the accepted one) for a concise description: stackoverflow.com/questions/16966043/…
-
Kim Homann about 5 yearsIn cplusplus.com/reference/memory/static_pointer_cast it says: "If sp is not empty, the returned object shares ownership over sp's resources, increasing by one the use count." Isn't it possible to pass by reference without increasing the ref count?
-
Bret Kuhns about 5 years@KimHomann the reference count would not increase in the example from my answer. Passing the pointed-to object by reference (eg,
const Foobar&
) does not causestd::shared_ptr
to affect the count. Usingstatic_pointer_cast
(ordynamic_point_cast
), however, will affect the reference count. The latter has to because it is participating in the lifetime of the object. -
Davis Herring almost 5 years
class
is for template parameters;struct
is for defining classes. (This is at most 45% a joke.) -
HankTheTank over 4 years@BretKuhns I have had a similar issue, and my derived class did not inherit public from the base class. Then the cast simply works. Also - I pass on a member object as a shared_ptr to a function as const ref to keep a reference, a perfectly reasonable scenario.
-
HankTheTank over 4 yearsIt is recommended to pass on shared pointers as const reference. I'Ve seen a talk of the developers explaining that a shared_ptr is not different from any other object, so const reference is the way to go.
-
Iron Attorney over 4 yearsAs has been mentioned, you should be able to pass const shared_ptr<Derrived>& as const shared_ptr<Base>& no problem, I do this all the time everywhere without thinking. Only time I ever run into problems is when I do one of the two mistakes mentioned in other answers by dshepherd and Phil Rosenberg. This should not be the accepted answer as the information is either wrong or misleading
-
Nathan Smiechowski about 4 yearsStupid compiler is stupid. This was my issue. Thank you!
-
Nathan Smiechowski about 4 years
-
Bret Kuhns about 4 years@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.
-
Alexis Paques over 3 yearsThis should definitely be considered the solution, there is no need for a cast as it is only missing public.
-
Pavel Nasevich over 2 yearsSaved 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?