14

I am currently having some troubles when using boost enable_shared_from_this and multiple inheritance.

The scenario can be described as follows:

  1. Class A implements some functionality and should inherit from enable_shared_from_this

  2. Class B implements another functionality and should inherit from enable_shared_from_this

  3. Class D inherits functionalities from A and B (class D : public A, public B {})

  4. When using some class B functionality from class D I got an exception (bad_weak_ptr)

  5. To inherit enable_shared_from_this from class D is not an option for me

I am not sure about how to solve this.

Oh, I am using Visual C++ 2010.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
ChrisPeterson
  • 419
  • 4
  • 15

3 Answers3

9

Expanding on Potatoswatter's solution, if you can change A and B to use a something slightly different than enable_shared_from_this. The code uses the standard library versions, but the boost implementation should be similar. Explanation below

#include <memory>
template<typename T>
struct enable_shared_from_this_virtual;

class enable_shared_from_this_virtual_base : public std::enable_shared_from_this<enable_shared_from_this_virtual_base>
{
    typedef std::enable_shared_from_this<enable_shared_from_this_virtual_base> base_type;
    template<typename T>
    friend struct enable_shared_from_this_virtual;

    std::shared_ptr<enable_shared_from_this_virtual_base> shared_from_this()
    {
        return base_type::shared_from_this();
    }
    std::shared_ptr<enable_shared_from_this_virtual_base const> shared_from_this() const
    {
       return base_type::shared_from_this();
    }
};

template<typename T>
struct enable_shared_from_this_virtual: virtual enable_shared_from_this_virtual_base
{
    typedef enable_shared_from_this_virtual_base base_type;

public:
    std::shared_ptr<T> shared_from_this()
    {
       std::shared_ptr<T> result(base_type::shared_from_this(), static_cast<T*>(this));
       return result;
    }

    std::shared_ptr<T const> shared_from_this() const
    {
        std::shared_ptr<T const> result(base_type::shared_from_this(), static_cast<T const*>(this));
        return result;
    }
};

So, the intent is that struct A would inherit publicly from enable_shared_from_this_virtual<A> and struct B would inherit publicly from enable_shared_from_this_virtual<B>. Since they both share the same common virtual object, there is only one std::enable_shared_from_this in the hierarchy.

When you call either classes shared_from_this, it performs a cast from enable_shared_from_this_virtual<T>* to T*, and uses the aliasing constructor of shared_ptr so that the new shared_ptr points to T* and shares ownership with the single shared pointer.

The use of the friend at the top is to prevent anyone from accessing the shared_from_this() members of the virtual base directly. They have to go through the ones provided by enable_shared_from_this_virtual<T>.

An example:

#include <iostream>

struct A : public enable_shared_from_this_virtual<A>
{
    void foo()
    {
        shared_from_this()->baz();
    }

    void baz()
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

struct B : public enable_shared_from_this_virtual<B>
{
    void bar()
    {
        shared_from_this()->baz();
    }

    void baz()
    {
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }
};

struct D: A, B {};


int main()
{
 std::shared_ptr<D> d(new D);
 d->foo();
 d->bar();
 return 0;
}
Dave S
  • 20,507
  • 3
  • 48
  • 68
  • In the second "shared_from_this function in enable_shared_from_this_virtual, did you mean it to return a shared_ptr? – Permaquid Feb 27 '14 at 14:19
  • From the many other implementations on the web I would recommend this one since it is simple enough and does not use `dynamic_cast`. The `dynamic_cast` is a big issue when using/developing shared libraries or when RTTI is not available. – CJCombrink Jan 29 '16 at 13:00
4

A shared_ptr is an observer to an invisible container object, or "control block." This container is always on the heap, never on the stack, and you can't get direct handle on it. Although shared_ptr and weak_ptr provide the only means of observation, there is still a hidden layer which owns the object. It is that hidden layer which populates enable_shared_from_this.

Multiple inheritance from enable_shared_from_this is not allowed because all the enable_shared_from_this bases would need to be initialized individually, but the control block has no way of getting a list of all base subobjects of a given type. All it can get is an ambiguous base error.

The only solution I see is to add a virtual base class of A and B which inherits from enable_shared_from_this. You might designate a particular class for this purpose:

struct shared_from_this_virtual_base
    : std::enable_shared_from_this< shared_from_this_virtual_base >
    {};

struct shared_from_this_base : virtual shared_from_this_virtual_base {};

struct A : shared_from_this_base { … };
struct B : shared_from_this_base { … };

But, there's another problem: you can't down-cast from a virtual base because it's ambiguous whether A or B contains the shared_from_this_virtual_base. To recover an A* or B* you would have to add those pointers to some kind of registry structure within shared_from_this_virtual_base. This would probably be populated by adding another CRTP pattern to shared_from_this_base. It's a bit more footwork than usual for C++ but I don't see any conceptual shortcut.

EDIT: Ah, the simplest way to get the downcast is to put a void* member in shared_from_this_virtual_base and then cast that to D* in whatever client code has knowledge of D. Quite workable, if not perfectly elegant. Or boost::any could provide a safer alternative.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • Thank you. What do you think about my comment in the Jonathan Wakely answer? Is that solution correct? – ChrisPeterson Feb 18 '13 at 16:17
  • @user1963961 It depends if `D` is the only derived class from `A`. The solution works if it is, but then the problem doesn't exist in the first place. If there are other derived classes, then you need to tell all the bases where to find `shared_from_this`. So that would mean CRTP for the bases `A` and `B`. Only viable if `A` and `B` are already mix-ins which help define a "bottom-heavy" implementation of just one `D`. – Potatoswatter Feb 18 '13 at 16:23
  • I don't like the explanation in this answer - `enable_shared_from_this` just holds a `weak_ptr` to a control block held elsewhere, it doesn't result in `delete this` or put ownership inside the object – Jonathan Wakely Feb 18 '13 at 16:42
  • @JonathanWakely I glossed over a bit. The control block is in practice held inside the shared object, although in theory it could be elsewhere. Taking for granted this implementation detail, the net effect is that `enable_shared_from_this` downcasts itself to the derived type and deletes the resulting pointer. My explanation may be too specific to reflect the Standard spec, but organizing the concepts this way makes the issue easier to see and I don't think I've lost anything or introduced any false dilemma. – Potatoswatter Feb 18 '13 at 16:51
  • The control block is not held inside the shared object in Boost's or GCC's implementation. Destroying the shared object is entirely orthogonal to `enable_shared_from_this` and unaffected by whether the shared object derives from it. Your explanation bears no resemblance whatsoever to the `shared_ptr` implementations I'm familiar with. – Jonathan Wakely Feb 18 '13 at 16:55
  • @JonathanWakely Sorry, the shared object is inside the control block. Whether you get to the control block pointer via a `weak_ptr` or a constant-offset downcast, the need for 1:1 correspondence remains. If you multiply inherited from `enable_shared_from_this`, what would the resulting structure look like in memory? One control block nested inside another? – Potatoswatter Feb 18 '13 at 17:03
  • Are you confusing `enable_shared_from_this` with `make_shared`? If my type inherits from `enable_shared_from_this` and I create it on the stack, how does it get inside a non-existent control block? – Jonathan Wakely Feb 18 '13 at 17:05
  • @JonathanWakely My understanding was that the same optimization applies equally to both cases, but you would know better than me. If you create an object derived from `enable_shared_from_this` on the stack, then you're screwed because it obviously can't actually be shared. – Potatoswatter Feb 18 '13 at 17:06
  • No, you're not screwed, it works perfectly. `enable_shared_from_this` has nothing to do with creating or destroying the object or memory layout, it's just a base class that contains a `weak_ptr` that shares ownership of `*this` _if `*this` is owned by a `shared_ptr`_ ... but `*this` doesn't have to be owned by any `shared_ptr` – Jonathan Wakely Feb 18 '13 at 17:07
  • @JonathanWakely Ah, so if you inherit `enable_shared_from_this` twice, then you are allowed to assign the object to a `shared_ptr` of (at most) one of two different types. Since it's my bedtime, I'll just delete that paragraph from the answer since it's inessential to the rest of the explanation, and the solution. – Potatoswatter Feb 18 '13 at 17:10
2

The standard is a bit vague about how shared_from_this is supposed to be implemented, but all implementations seem to agree, because Boost provides a reference implementation.

When you create boost::shared_ptr<D> myD(new D()) the shared_ptr constructor checks if there is an unambiguous conversion from D* to enable_shared_from_this<X>* for some X (which implies that D has a base class of type enable_shared_from_this<X>). If the conversion works then a weak_ptr<X> in the base class will be set to refer to the newly-created shared_ptr.

In your code there are two possible conversions, to enable_shared_from_this<A> and enable_shared_from_this<B>, which is ambiguous, so no weak_ptr gets set to refer to myD. That means if a member function of B later calls shared_from_this() it will get a bad_weak_ptr exception, because its enable_shared_from_this<B> member was never set.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521