Увеличить shared_from_this и множественное наследование
В настоящее время у меня возникают некоторые проблемы при использовании boost enable_shared_from_this
и множественного наследования.
Сценарий может быть описан следующим образом:
Класс A
реализует некоторую функциональность и должен наследоваться от enable_shared_from_this
Класс B
реализует другую функциональность и должен наследоваться от enable_shared_from_this
Класс D
наследует функциональные возможности от A
и B
(class D : public A, public B {}
)
При использовании некоторой функциональности класса B
из класса D
я получил исключение (bad_weak_ptr
)
Унаследовать 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>
никогда не был установлен.