What is the rationale for the difference in destruction behavior between std :: unique_ptr and std :: shared_ptr?

From http://en.cppreference.com/w/cpp/memory/unique_ptr :

If T is a derived class (sic) of some base B , then std::unique_ptr<T> is implicitly convertible to std::unique_ptr<B> . The default deficit resulting from std::unique_ptr<B> will use the delete operator for B , resulting in undefined behavior if the destructor of B is virtual. Note that std::shared_ptr behaves differently: std::shared_ptr<B> will use the delete operator for type T , and the object belonging to it will be deleted correctly even if the destructor B not virtual.

What is the rationale for the differences in kill behavior described above? My initial guess is performance?

It is also interesting to know how std::shared_ptr<B> can call a destructor of type T if the destructor on B not virtual and cannot be called, as far as I can see from the context std::shared_ptr<B> ?

+7
c ++ c ++ 14
source share
1 answer

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.

+8
source share

All Articles