Можно ли вывести из std:: enable_shared_from_this и абстрактного базового класса?
Я пишу класс, который должен выводиться из абстрактного базового класса. Я не могу изменить абстрактный базовый класс. Класс будет считаться shared_ptr
для абстрактного базового класса. Достаточно ли наследовать от абстрактного базового класса и enable_shared_from_this
? Вот так:
class IWidget {
public:
virtual ~IWidget(){}
// ...
};
class Widget : public std::enable_shared_from_this<Widget>, public IWidget {
protected:
Widget(); // protected, use create
public:
static std::shared_ptr<IWidget> create() {
return std::shared_ptr<IWidget>(new Widget(init));
}
// ...
};
Более полный код здесь, который, кажется, работает.
Большинство примеров, которые я могу найти в enable_shared_from_this
, имеют его в базовом классе. В этом случае я не могу изменить базовый класс. Можно ли использовать множественное наследование и использовать его в производном классе?
Меня немного беспокоило, что я могу только гарантировать, что enable_shared_from_this
будет работать, только если я создал shared_ptr<Widget>
, но в этом случае я создаю shared_ptr<IWidget>
.
Обновление: Интересно, что если я изменю метод create
на:
IWidget* w = new Widget(init);
return std::shared_ptr<IWidget>(w);
Я получаю ошибку во время выполнения, когда я пытаюсь использовать shared_from_this()
. Я думаю, это имеет смысл. shared_ptr
имеет шаблонный конструктор, который принимает "конвертируемый" указатель. И если конструктор shared_ptr
не знает, что он принимает Widget
, он не знает, что он происходит от enable_shared_from_this
, и он не может хранить weak_ptr
. Мне просто интересно, зарегистрировано ли это поведение.
Ответы
Ответ 1
Да, это абсолютно нормально.
shared_ptr
имеет шаблонный конструктор, который принимает "конвертируемый" указатель. И если конструктор shared_ptr
не знает, что он принимает Widget
, он не знает, что он происходит от enable_shared_from_this
и он не может хранить weak_ptr
.
Правильно.
Мне просто интересно, зарегистрировано ли это поведение.
В текущем стандарте enable_shared_from_this
очень плохо указана, но см. P0033 для новой и улучшенной спецификации enable_shared_from_this
, которая будет в С++ 17. А также, отвечая на вопрос "что произойдет, если вы сделаете это дважды?" вопрос, пересмотренная формулировка точно определяет, как используется базовый класс enable_shared_from_this
, и как инициализируется элемент weak_ptr
. Эта часть новой формулировки - это просто стандартизация существующей практики, т.е. Это просто более точное описание того, что уже делают реальные реализации. (Ответ на вопрос "что происходит, если вы делаете это дважды?", Отличается от того, что было сделано ранее, но по уважительным причинам, и это не имеет никакого отношения к вашему вопросу).
Новая спецификация уточняет, что ваш оригинальный пример полностью корректен и корректен.
В текущем стандарте указано, что измененная версия в вашем обновленном вопросе имеет поведение undefined, когда вы вызываете shared_from_this()
, из-за нарушения предварительного условия, что существует shared_ptr
, которому принадлежит указатель на производный (поскольку вы создаете a shared_ptr
, которому принадлежит указатель на базу). Однако этого предположения недостаточно для обеспечения разумной семантики, как объяснено в статье. Пересмотренная формулировка делает вашу измененную версию также корректной (т.е. Поведение undefined), но weak_ptr
в базовом классе не будет разделять право собственности на shared_ptr<IWidget>
, и поэтому shared_from_this()
выдаст исключение (что и есть вы наблюдаете из своей реализации).
Ответ 2
Да. Это будет хорошо, если никто не будет полагаться на поведение undefined. Теперь, строго говоря, ваш код уже сломан, если вы используете UB, но на практике множественное наследование делает много недопустимых предположений, которые люди делают более очевидными.
Я особенно думаю о
Base* p = this;
Derived* pDerived = reinterpret_cast<Derived*>(p);
Может работать не так, как ожидалось. Это должно быть static_cast
.