shared_ptr helps you only in sharing ownership without putting anything into your class, it doesn't care about your inheritance. shared_ptr is non-intrusive and knows only about the Base class so it will delete your ptr only as a Base class pointer. Its your responsibility in this case to ensure correct deletion by putting a virtual destructor into your Base class. It would be impossible to ensure correct deletion the pointer class. Sorry, I wasn't right at this point, its possible to handle this with a template constructor that stores an additional deleter object inside your pointer (the deleter object has the virtual destructor instead of your class). Still, this is more error prone than using intrusive pointers most of the time.
Demonstrating the usage of a deleter object with a template ctor:
class Deleter
{
public:
virtual ~Deleter() {}
};
template <typename Deletable>
class TDeleter : public Deleter
{
public:
TDeleter(Deletable* p)
: m_Ptr(p)
{}
~TDeleter()
{
delete m_Ptr;
}
private:
Deletable* m_Ptr;
};
template <typename T>
class Ptr
{
public:
template <typename U>
Ptr(U* p)
{
m_Deleter = new TDeleter<U>(p);
m_Ptr = p;
}
~Ptr()
{
delete m_Deleter;
}
T* operator->() { return m_Ptr; }
const T* operator->() const { return m_Ptr; }
T& operator*() { return *m_Ptr; }
const T& operator*() const { return *m_Ptr; }
private:
Ptr(const Ptr&);
private:
Deleter* m_Deleter;
T* m_Ptr;
};
class C
{
public:
C() { printf("%s\n", __FUNCTION__); }
~C() { printf("%s\n", __FUNCTION__); }
void BaseMethod() { printf("%s\n", __FUNCTION__); }
};
class D : public C
{
public:
D() { printf("%s\n", __FUNCTION__); }
~D() { printf("%s\n", __FUNCTION__); }
};
void TestPtr()
{
Ptr<C> p(new D);
p->BaseMethod();
}
I like intrusive shared pointers much more. An intrusive shared pointer expects your class to handle its own refcounting by itself so an intrusive shared pointer expects your class to have its own AddRef/Release methods (or whatever you name them). So when an intrusive shared pointer takes ownership of your object then it calls its AddRef. When the pointer lets the object go aways then it calls its release method. For this to work the object either implements AddRef/Release itself, or usually instead of this you derive your object from a base class that already implements AddRef/Release along with refcounting and besides this it introduces a virtual destructor for you that automatically helps you to avoid destruction bugs. Such an intrusive shared pointer can easily be implemented so that it handles inheritance well, automatic upcast can work between such pointers without problems. Another nice feature can be in case of the base class you use in your objects that you can provide two implementations: one for single threaded use, and another one for multithreaded use that handles the reference count with atomic operations (__sync_add_and_fetch, InterlockedXXX, ...).
EDIT: I usually use only the following pointers in my own codebase:
- Intrusive shared pointer (single/multithreaded)
- Intrusive weak pointer
- auto pointer
Note that an object can be the target of both shared/weak pointers as long as all the shared/weak pointer references come from the same thread because using weak pointers in case of multithreading has no point.
EDIT: Both intrusive and nonintrusive pointers have their advantages. Nonintrusives are very useful when the referenced classes are not yours. Intrusive ones are less vulnerable to transferring "raw" c++ pointers from one reference into another. My experience is that the latter thing can cause very hard to find bugs and systems that work with intrusive ptrs are less buggy. This is of course just my observation/opinion.