134

Update: the shared_ptr in this example is like the one in Boost, but it doesn't support shared_polymorphic_downcast (or dynamic_pointer_cast or static_pointer_cast for that matter)!

I'm trying to initialize a shared pointer to a derived class without losing the reference count:

struct Base { };
struct Derived : public Base { };
shared_ptr<Base> base(new Base());
shared_ptr<Derived> derived;

// error: invalid conversion from 'Base* const' to 'Derived*'
derived = base;  

So far, so good. I didn't expect C++ to implicitly convert Base* to Derived*. However, I do want the functionality expressed by the code (that is, maintaining the reference count while downcasting the base pointer). My first thought was to provide a cast operator in Base so that an implicit conversion to Derived could take place (for pedants: I would check that the down cast is valid, don't worry):

struct Base {
  operator Derived* ();
}
// ...
Base::operator Derived* () {
  return down_cast<Derived*>(this);
}

Well, it didn't help. It seems the compiler completely ignored my typecast operator. Any ideas how I could make the shared_ptr assignment work? For extra points: what kind of type Base* const is? const Base* I understand, but Base* const? What does const refer to in this case?

Lajos Nagy
  • 9,075
  • 11
  • 44
  • 55
  • Why do you need a shared_ptr, instead of shared_ptr? – Bill Aug 31 '09 at 16:38
  • 7
    Because I want to access functionality in Derived that's not in Base, without cloning the object (I want a single object, referenced by two shared pointers). By the way, why don't the cast operators work? – Lajos Nagy Sep 04 '09 at 17:28

4 Answers4

160

You can use dynamic_pointer_cast. It is supported by std::shared_ptr.

std::shared_ptr<Base> base (new Derived());
std::shared_ptr<Derived> derived =
               std::dynamic_pointer_cast<Derived> (base);

Documentation: https://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast

Also, I don't recommend using cast operator in the base class. Implicit casting like this may become the source of bugs and errors.

-Update: If the type is not polymorphic, std::static_pointer_cast may be used.

Jo Ham
  • 149
  • 1
  • 11
Masood Khaari
  • 2,911
  • 2
  • 23
  • 40
  • 5
    I didn't understand from the first line that he's not using `std::shared_ptr`. But from the comments of the first answer I inferred that he's not using boost, so he may be using `std::shared_ptr`. – Masood Khaari Jan 27 '13 at 08:57
  • OK. Sorry. He should better clarified that he's using a custom implementation. – Masood Khaari Jan 27 '13 at 09:10
51

I assume you're using boost::shared_ptr... I think you want dynamic_pointer_cast or shared_polymorphic_downcast.

However, these require polymorphic types.

what kind of type Base* const is? const Base* I understand, but Base* const? What does const refer to in this case?

  • const Base * is a mutable pointer to a constant Base.
  • Base const * is a mutable pointer to a constant Base.
  • Base * const is a constant pointer to a mutable Base.
  • Base const * const is a constant pointer to a constant Base.

Here's a minimal example:

struct Base { virtual ~Base() { } };   // dynamic casts require polymorphic types
struct Derived : public Base { };

boost::shared_ptr<Base> base(new Base());
boost::shared_ptr<Derived> derived;
derived = boost::static_pointer_cast<Derived>(base);
derived = boost::dynamic_pointer_cast<Derived>(base);
derived = boost::shared_polymorphic_downcast<Derived>(base);

I'm not sure if it was intentional that your example creates an instance of the base type and casts it, but it serves to illustrate the difference nicely.

The static_pointer_cast will "just do it". This will result in undefined behavior (a Derived* pointing at memory allocated for and initialized by Base) and will likely cause a crash, or worse. The reference count on base will be incremented.

The dynamic_pointer_cast will result in a null pointer. The reference count on base will be unchanged.

The shared_polymorphic_downcast will have the same result as a static cast, but will trigger an assertion, rather than seeming to succeed and leading to undefined behavior. The reference count on base will be incremented.

See (dead link):

Sometimes it is a little hard to decide whether to use static_cast or dynamic_cast, and you wish you could have a little bit of both worlds. It is well known that dynamic_cast has a runtime overhead, but it is safer, whereas static_cast has no overhead at all, but it may fail silently. How nice it would be if you could use shared_dynamic_cast in debug builds, and shared_static_cast in release builds. Well, such a thing is already available and is called shared_polymorphic_downcast.

dddJewelsbbb
  • 626
  • 4
  • 17
Tim Sylvester
  • 22,897
  • 2
  • 80
  • 94
  • Unfortunately, your solution depends on Boost functionality that was deliberately excluded from the particular shared_ptr implementation we're using (don't ask why). As for the const explanation, it makes much more sense now. – Lajos Nagy Aug 31 '09 at 17:44
  • 3
    Short of implementing the other `shared_ptr` constructors (taking `static_cast_tag` and `dynamic_cast_tag`), there's not much you can do. Anything you do outside `shared_ptr` will not be able to manage the refcount. -- In a "perfect" OO design you can always use the base type, and never need to know nor care what the derived type is, because all its functionality is exposed through base-class interfaces. Perhaps you just need to re-think why you need to down-cast in the first place. – Tim Sylvester Aug 31 '09 at 20:18
  • 1
    @Tim Sylvester: but, C++ is not a "perfect" OO language! :-) down-casts have their place in a non-perfect OO language – Steve Folly Aug 31 '09 at 22:17
5

If somebody gets here with boost::shared_ptr...

This is how you can downcast to the derived Boost shared_ptr. Assuming Derived inherits from Base.

boost::shared_ptr<Base> bS;
bS.reset(new Derived());

boost::shared_ptr<Derived> dS = boost::dynamic_pointer_cast<Derived,Base>(bS);
std::cout << "DerivedSPtr  is: " << std::boolalpha << (dS.get() != 0) << std::endl;

Make sure 'Base' class/struct has at least one virtual function. A virtual destructor also works.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mitendra
  • 1,426
  • 17
  • 11
0

I had the same issue this week and my solution do not involve the new kinds of casting available only in the modern c++ (>= 20). I only used reinterpret_cast with a const & of the shared ptr created in between the conversion as shown in the code below:

std::shared_ptr<Base> basePtr = createBasePtr();
std::shared_ptr<Derived> derivedPtr = reinterpret_cast<const std::shared_ptr<Derived>&>(basePtr);