tags:

views:

1268

answers:

2

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?

+10  A: 

I assume you're using boost::shared_ptr...

I think you want dynamic_pointer_cast or shared_polymorphic_downcast.

These require polymorphic types, however.

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

Update:

Sorry, I misread your second question before:

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

Update:

I'm sure you want to know the difference. 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 an invalid pointer and will likely cause a crash. The reference count on base will be incremented.

The dynamic_pointer_cast will simply come out NULL. The reference count on base will be unchanged.

The shared_polymorphic_downcast will come the same as a static cast, but will trigger an assertion in the process. The reference count on base will be incremented.

See http://www.tweakbits.com/articles/sharedptr/index.html:

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.

Tim Sylvester
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
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
@Tim Sylvester: but, C++ is not a "perfect" OO language! :-) down-casts have their place in a non-perfect OO language
Steve Folly
A: 

Any way you do it will be unsafe. Base is not a Derived, so in general it would not be allowed to do that cast. No framework designed to be safe (like boost) will allow this. Types can be safely cast to parent types, but not the other way around unless you have some way to know for sure that it was instantiated as the derived type (or something derived from the derived type). In your example, it was not.

This is c++ though, so if you have confidence in your ability to point a gun at your foot and pull the trigger without hitting yourself, there is always a way. boost::static_pointer_cast will do what you are asking for, for better or for worse. If you can modify your shared_ptr implementation, you should be able to add that without requiring any form of RTTI. It's basically a glorified C-Style cast on a pointer type. It's unsafe, but the compiler will let you do it.

If your Derived class adds any data members, then accessing those will cause a crash or memory corruption. If you add any virtual member functions, those will not work (since the object was created with the wrong v_table). That includes the destructor. Non-virtual member functions should work, but I can't think of any reason offhand why that would be useful. It will only work in code that you are compiling, and therefor code that you could change. Are you trying to access protected class members in Base in an object instantiated in a library that you can't change?

The short answer is to redesign your approach so that you don't need to do this.

Alan
How about: if (o->kind() == DERIVED) { /* Do cast. */ } Yet, I cannot do it with shared pointers (and I'm 100% confident that the cast is correct).
Lajos Nagy
Besides, one can always use CheckedCast<T>(p) where CheckedCast performs the same `kind check' before performing the cast (poor man's RTTI without RTTI).
Lajos Nagy