Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

boost shared_from_this and multiple inheritance

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.

like image 580
ChrisPeterson Avatar asked Feb 18 '13 14:02

ChrisPeterson


3 Answers

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;
}
like image 98
Dave S Avatar answered Oct 17 '22 23:10

Dave S


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.

like image 32
Potatoswatter Avatar answered Oct 17 '22 22:10

Potatoswatter


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.

like image 2
Jonathan Wakely Avatar answered Oct 17 '22 23:10

Jonathan Wakely