My class inherits from multiple bases, one of which is std::enable_shared_from_this
. Must it be the first base?
Suppose the following example code:
struct A { ~A(); };
struct B { ~B(); };
struct C : A, B, std::enable_shared_from_this<C> {};
std::make_shared<C>();
When ~A()
and ~B()
run, can I be sure that the storage where C
lived is still present?
std::enable_shared_from_this is a standard solution that enables a shared_ptr managed object to acquire a shared_ptr to itself on demand. A class T that publicly inherits an std::enable_shared_from_this<T> encapsulates a std::weak_ptr<T> to itself that can be converted to a std::shared_ptr<T> when needed.
std::enable_shared_from_this allows an object t that is currently managed by a std::shared_ptr named pt to safely generate additional std::shared_ptr instances pt1, pt2, ... that all share ownership of t with pt .
When ~A() and ~B() run, can I be sure that the storage where C lived is still present?
No, and the order of the base classes is irrelevant. Even the use (or not) of enable_shared_from_this is irrelevnt.
When a C object is destroyed (however that happens), ~C()
will be called before both ~A()
and ~B()
, as that is the way that base destructors work. If you try to "reconstruct" the C object in either base destructor and access fields in it, those fields will have already been destroyed, so you will get undefined behavior.
When
~A()
and~B()
run, can I be sure that the storage whereC
lived is still present?
Of course! It would be hard to use a base class that tries to free its own memory (the memory where it resides). I'm not sure it's even formally legal.
Implementations don't do that: when a shared_ptr<T>
is destructed or reset, the reference count (RC) for the shared ownership of T
is decremented (atomically); if it reached 0 in the decrement, then destruction/deletion of T
is started.
Then the weak-owners-or-T-exists count is decremented (atomically), as T
no longer exists: we need to know if we are the last entity standing interested in the control block; if the decrement gave a non zero result, it means some weak_ptr
exist that share (could be 1 share, or 100%) ownership of control block, and they are now responsible for the deallocation.
Either way, atomic decrement will at some point end up with a zero value, for the last co-owner.
Here there are no threads, no non-determinism, and obviously the last weak_ptr<T>
was destroyed during destruction of C
. (The unwritten assumption in your question being that no other weak_ptr<T>
was kept.)
Destruction always happen in that exact order. The control block is used for destruction, as no shared_ptr<T>
knows (in general) which (potentially non virtual) destructor of (potentially different) most derived class to call. (The control block also knows not to deallocate memory on shared count reaching zero for make_shared
.)
The only practical variation between implementations seems to be about the fine details of memory fences and avoiding some atomic operations in common cases.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With