Увеличить shared_from_this и множественное наследование

В настоящее время у меня возникают некоторые проблемы при использовании boost enable_shared_from_this и множественного наследования.

Сценарий может быть описан следующим образом:

  1. Класс A реализует некоторую функциональность и должен наследоваться от enable_shared_from_this

  2. Класс B реализует другую функциональность и должен наследоваться от enable_shared_from_this

  3. Класс D наследует функциональные возможности от A и B (class D : public A, public B {})

  4. При использовании некоторой функциональности класса B из класса D я получил исключение (bad_weak_ptr)

  5. Унаследовать enable_shared_from_this от класса D для меня не вариант

Я не уверен, как решить эту проблему.

О, я использую Visual C++ 2010.

Ответы

Ответ 1

Расширение решения Potatoswatter, если вы можете изменить A и B, чтобы использовать что-то немного отличное от enable_shared_from_this. Код использует стандартные версии библиотеки, но реализация boost должна быть аналогичной. Пояснение ниже

#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;
    }
};

Итак, цель состоит в том, чтобы struct A публично наследовал бы от enable_shared_from_this_virtual<A>, а struct B публично наследовал бы от enable_shared_from_this_virtual<B>. Так как они оба имеют один и тот же общий виртуальный объект, в иерархии есть только один std::enable_shared_from_this.

Когда вы вызываете оба класса shared_from_this, он выполняет трансляцию из enable_shared_from_this_virtual<T>* в T * и использует конструктор aliasing для shared_ptr, чтобы новый shared_ptr указывал на T * и владел долей с помощью единого общего указателя.

Использование друга наверху - это запретить кому-либо доступ к элементам shared_from_this() виртуальной базы напрямую. Они должны пройти через те, которые предоставляются enable_shared_from_this_virtual<T>.

Пример:

#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;
}

Ответ 2

A shared_ptr является наблюдателем к невидимому объекту-контейнеру или "блоку управления". Этот контейнер всегда находится в куче, никогда в стеке, и вы не можете получить прямую ручку на нем. Хотя shared_ptr и weak_ptr обеспечивают единственное средство наблюдения, все еще остается скрытый слой, которому принадлежит объект. Это скрытый слой, который заполняет enable_shared_from_this.

Множественное наследование из enable_shared_from_this недопустимо, так как все базы enable_shared_from_this должны быть инициализированы индивидуально, но блок управления не имеет способа получить список всех базовых подобъектов данного типа. Все, что он может получить, это двусмысленная ошибка базы.

Единственное решение, которое я вижу, - добавить базовый класс virtual A и B, который наследует от enable_shared_from_this. Для этой цели можно назначить определенный класс:

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 { … };

Но есть еще одна проблема: вы не можете сбрасывать из базы virtual, потому что она неоднозначна, если A или B содержит shared_from_this_virtual_base. Чтобы восстановить A* или B*, вам придется добавить эти указатели в какую-то структуру реестра в shared_from_this_virtual_base. Вероятно, это будет заполнено добавлением другого шаблона CRTP в shared_from_this_base. Это немного больше, чем обычно для С++, но я не вижу концептуального ярлыка.

EDIT: А, самый простой способ получить downcast - поместить член void* в shared_from_this_virtual_base, а затем применить его к D* в том, что код клиента имеет знания D. Совершенно практичный, если не совсем элегантный. Или boost::any может обеспечить более безопасную альтернативу.

Ответ 3

Стандарт немного неопределен относительно того, как предполагается, что shared_from_this будет реализован, но все реализации, похоже, согласны, поскольку Boost предоставляет ссылочную реализацию.

При создании boost::shared_ptr<D> myD(new D()) конструктор shared_ptr проверяет, существует ли однозначное преобразование из D* в enable_shared_from_this<X>* для некоторого X (что подразумевает, что D имеет базовый класс типа enable_shared_from_this<X>). Если преобразование работает, то weak_ptr<X> в базовом классе будет настроен для ссылки на вновь созданный shared_ptr.

В вашем коде есть два возможных преобразования: enable_shared_from_this<A> и enable_shared_from_this<B>, что является неоднозначным, поэтому no weak_ptr устанавливается для ссылки на myD. Это означает, что если функция-член B позже вызывает shared_from_this(), она получит исключение bad_weak_ptr, потому что элемент enable_shared_from_this<B> никогда не был установлен.