c++shared-ptrmultiple-inheritanceweak-ptrenable-shared-from-this

Could shared_from_this be implemented without enable_shared_from_this?


There seem to be some edge-cases when using enabled_shared_from_this. For example:

boost shared_from_this and multiple inheritance

Could shared_from_this be implemented without using enable_shared_from_this? If so, could it be made as fast?


Solution

  • A shared_ptr is 3 things. It is a reference counter, a destroyer and an owned resource.

    When you make_shared, it allocates all 3 at once, then constructs them in that one block.

    When you create a shared_ptr<T> from a T*, you create the reference counter/destroyer separately, and note that the owned resource is the T*.

    The goal of shared_from_this is that we can extract a shared_ptr<T> from a T* basically (under the assumption it exists).

    If all shared pointers where created via make_shared, this would be easy (unless you want defined behavior on failure), as the layout is easy.

    However, not all shared pointers are created that way. Sometimes you can create a shared pointer to an object that was not created by any std library function, and hence the T* is unrelated to the shared pointer reference counting and destruction data.

    As there is no room in a T* or what it points to (in general) to find such constructs, we would have to store it externally, which means global state and thread safety overhead and other pain. This would be a burden on people who do not need shared_from_this, and a performance hit compared to the current state for people who do need it (the mutex, the lookup, etc).

    The current design stores a weak_ptr<T> in the enable_shared_from_this<T>. This weak_ptr is initialized whenever make_shared or shared_ptr<T> ctor is called. Now we can create a shared_ptr<T> from the T* because we have "made room" for it in the class by inheriting from enable_shared_from_this<T>.

    This is again extremely low cost, and handles the simple cases very well. We end up with an overhead of one weak_ptr<T> over the baseline cost of a T.

    When you have two different shared_from_this, their weak_ptr<A> and weak_ptr<B> members are unrelated, so it is ambiguous where you want to store the resulting smart pointer (probably both?). This ambiguity results in the error you see, as it assumes there is exactly one weak_ptr<?> member in one unique shared_from_this<?> and there is actually two.

    The linked solution provides a clever way to extend this. It writes enable_shared_from_this_virtual<T>.

    Here instead of storing a weak_ptr<T>, we store a weak_ptr<Q> where Q is a virtual base class of enable_shared_from_this_virtual<T>, and does so uniquely in a virtual base class. It then non-virtually overrides shared_from_this and similar methods to provide the same interface as shared_from_this<T> does using the "member pointer or child type shared_ptr constructor", where you split the reference count/destroyer component from the owned resource component, in a type-safe way.

    The overhead here is greater than the basic shared_from_this: it has virtual inheritance and forces a virtual destructor, which means the object stores a pointer to a virtual function table, and access to shared_from_this is slower as it requires a virtual function table dispatch.

    The advantage is it "just works". There is now one unique shared_from_this<?> in the heirarchy, and you can still get type-safe shared pointers to classes T that inherit from shared_from_this<T>.