views:

521

answers:

4

Do smart pointers handle down casting, and if not what is a safe way of working around this limitation?

An example of what I'm trying to do is having two STL vectors (for example) containing smart pointers. The first contains smart pointers to a base class while the second contains smart pointers to a derived class. The smart pointers are referenced counted, e.g. similar behaviour to Boost's shared_ptrs, but hand-rolled. I've included some sample code that I whipped up to provide an example:

vector<CBaseSmartPtr> vecBase;
vector<CDerivedSmartPtr> vecDer;
...
CBaseSmartPtr first = vecBase.front();
vecDer.push_back(CDerivedSmartPtr(dynamic_cast<CDerived*>(first.get()));

This seems not safe to me, as I think I'm ending up with two smart pointers managing the same object. At some point down the track this is probably going to result in one of them freeing the object while the other still holds references to it.

What I'd hope for but don't think will work is a straight down-cast while keeping the same object, e.g.

dynamic_cast<CDerivedSmartPtr>(first)

Should I be looking to change the second container to also use CBaseSmartPtr and downcast on usage only? Are there other solutions?

A: 

Normal smart pointers, like std::auto_ptr, are not safe to use in STL containers, due to ownership being moved around when the STL assigns instances of smart pointers to each other as it copies data around internally. You need to use something like boost::shared_ptr instead, which internally implements reference counting to ensure an object stays alive no matter how many smart pointer instances refer to it. If you are writing your own smart pointer types, then you need to implement similar reference counting.

Remy Lebeau - TeamB
Cheers, I'll add a comment to the question making it explicit that I plan to use reference counted smart pointers.
dlanod
+2  A: 

The property you want is covariance in the pointed-to type. That is, if D isa B, then you want smartptr<D> isa smartptr<B>. I don't think this is elegantly supported at all in C++, but as always, there are template/overload hacks available.

http://www.boost.org/doc/libs/1%5F39%5F0/libs/smart%5Fptr/pointer%5Fcast.html gives a dynamic cast that works on regular and boost::smart_ptr. You should learn from the implementation if you don't want to just use Boost's.

wrang-wrang
+1 for `dynamic_pointer_cast` and -1 for saying C++ doesn't have covariant return types. See here: http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.8
Kristo
@Kristo: I'm with you on both, but I still think this is very good advice. @wang-wang: Why don't you remove the false statement? It would make it easier for others to upvote this, otherwise good, answer.
sbi
You're right - I didn't realize virtual methods allowed covariant return types! But it is true that generic types e.g. ptr<A> vector<A> don't support covariance (like Java does, albeit by type erasure).
wrang-wrang
@wang-wang: Yes, `std::vector<Derived*>` to `std::vector<Base*>` conversions aren't possible using only the core language. I see this as a deficiency of C++, too.
sbi
+1  A: 

Follow the thread here in one of the boost mailing lists. It shows how one can implement smart-pointer downcasting in case of boost::shared_ptr. HTH

Abhay
That looks downright scary. He's casting a shared_ptr<T>* to a shared_ptr<U>*. See also Rainer Deyke's response.
MSalters
Hmmm, yes. Thanks for the update.
Abhay
+4  A: 

Smart pointers can handle downcasting, but it's not automatic. And getting const-correctness in can be a bit complex (I've used our smart pointer implementation in interview questions, there's some template trickery involved). But many users of smart pointers never instantiate their smart pointers with const-qualified types anyway.

The first thing you need to get correct is the counter. Since you may need to share a counter between smart_ptr<Base> and smart_ptr<Derived>, the counter type should not depend on the type argument. In general, this is not a big deal anyway. A counter is merely a size_t, probably wrapped in a class. (Note: there are alternative smart pointer designs, but the question strongly suggests a counter is used)

A cast towards base should be fairly trivial. Hence, your smart_ptr should have a constructor taking a smart_ptr. In this ctor, add a line static_cast<T*>((U*)0);. This doesn't generate code, but prevents instantiation when T is not a base of U (modulo const qualifications).

The other way around should be an explicit cast. You can't programatically enumerate all bases of T, so smart_ptr<T> cannot derive from smart_ptr<Base1_of_T>, smart_ptr<Base2_of_T>, ... Hence, a dynamic_cast<smart_ptr<T> > won't work. You can provide your own smart_dynamic_cast<SPT>(smart_ptr<U> const& pU). This is best implemented as a function returing an SPT. In this function, you can simply do a return SPT(dynamic_cast<SPT::value_type*>(&*pU)).

MSalters
Actually, I think you cann programmatically enumerate bases. Hasn't Andrej shown us how to do this?
sbi
Got the book, but not here. I don't remember such a thing, though. Just to remove any confusion: The goal of "enumerating bases" is to determine a set of types B1..Bn given a type D, such that the relation `is_base_and_derived<B,D>` holds for all types B1..Bn and no other types.
MSalters
@MSalters: Ah brainfart of mine. I was thinking of enumerating types and making them a base class... Sorry.
sbi