std::shared_ptr<X> already has tons of overhead over raw B* .
A shared_ptr<X> basically supports 4 things. It supports a pointer to B , it supports two reference counts (a βhardβ reference count and a βsoftβ for weak_ptr ), and it supports a cleanup function.
The cleanup function is why shared_ptr<X> behaves differently. When you create shared_ptr<X> , a function that calls the destructor of a particular type is created and stored in a cleanup function controlled by shared_ptr<X> .
When changing managed types ( B* becomes C* ), the cleanup function remains unchanged.
Since shared_ptr<X> needs to manage reference counts, the additional overhead of this storage of cleanup functions is negligible.
With a unique_ptr<B> class is almost as cheap as raw B* . It maintains a zero state, different from its B* , and its behavior (when destroyed) is reduced to if (b) delete b; . (Yes, this if (b) redundant, but the optimizer can figure it out.)
To support base-based creation and delete-as-output, you need to maintain an additional state that remembers that unique_ptr really belongs to the derived class. This may be in the form of a stored pointer to delete, for example a shared_ptr .
This, however, unique_ptr<B> size of unique_ptr<B> or requires it to store data on the heap somewhere.
It was decided that unique_ptr<B> should have zero service data and, as such, does not support database binding, but still calls the database destructor.
Now you can teach unique_ptr<B> to do this by simply adding a deleter type and saving the destruction function, which knows the type of thing that it destroys. Above we talked about the default default unique_ptr , which is stateless and trivial.
struct deleter { void* state; void(*f)(void*); void operator()(void*)const{if (f) f(state);} deleter(deleter const&)=default; deleter(deleter&&o):deleter(o) { o.state = nullptr; of=nullptr; } deleter()=delete; template<class T> deleter(T*t): state(t), f([](void*p){delete static_cast<T*>(p);}) {} }; template<class T> using smart_unique_ptr = std::unique_ptr<T, deleter>; template<class T, class...Args> smart_unique_ptr<T> make_smart_unique( Args&&... args ) { T* t = new T(std::forward<Args>(args)...); return { t, t }; }
live example where I generate a unique -ptr for a derivative, save it in a unique-ptr for the base, and then reset the base. The displayed pointer is deleted.
(A simple void(*)(void*) debugger may encounter problems in which the one passed to void* will differ in value between the base and derived cases.)
Please note that changing the pointer stored in such unique_ptr without changing the deaeer will result in careless behavior.