Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing shared_ptr<Derived> as shared_ptr<Base>

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.

like image 542
Matt Kline Avatar asked Nov 15 '12 18:11

Matt Kline


People also ask

Can shared_ptr be copied?

The ownership of an object can only be shared with another shared_ptr by copy constructing or copy assigning its value to another shared_ptr . Constructing a new shared_ptr using the raw underlying pointer owned by another shared_ptr leads to undefined behavior.

What does shared_ptr get () do?

A shared_ptr may share ownership of an object while storing a pointer to another object. get() returns the stored pointer, not the managed pointer.

Can shared_ptr be Nullptr?

A null shared_ptr does serve the same purpose as a raw null pointer. It might indicate the non-availability of data. However, for the most part, there is no reason for a null shared_ptr to possess a control block or a managed nullptr .

What happens when you move a shared_ptr?

By moving the shared_ptr instead of copying it, we "steal" the atomic reference count and we nullify the other shared_ptr . "stealing" the reference count is not atomic, and it is hundred times faster than copying the shared_ptr (and causing atomic reference increment or decrement).


2 Answers

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 { }; 
like image 145
dshepherd Avatar answered Nov 15 '22 16:11

dshepherd


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

like image 22
Bret Kuhns Avatar answered Nov 15 '22 18:11

Bret Kuhns