Можно ли использовать enable_shared_from_this без наследования?

Примеры, которые я нашел для enable_shared_from_this, показывают, что он используется через наследование. Например:

struct Good : enable_shared_from_this<Good> {
    shared_ptr<Good> getptr() {
        return shared_from_this();
    }
};

int main() {
    // Good: the two shared_ptr's share the same object
    shared_ptr<Good> gp1(new Good);
    shared_ptr<Good> gp2 = gp1->getptr();
    cout << "gp2.use_count() = " << gp2.use_count() << endl;
}

В свое время меня много раз предупреждали об опасностях наследования от стандартной библиотеки. Этот код определенно разделяет эти опасности, например:

struct A : enable_shared_from_this<A> {};
struct B : enable_shared_from_this<B> {};

Если я хочу создать struct C : A, B {};, камнем преткновения, очевидно, будет C::shared_from_this(). Очевидно, что мы можем обойти это, но есть некоторая внутренняя сложность.

Итак, мой вопрос: есть ли способ использовать enable_shard_from_this как отношение «имеет», а не как «есть»?


person Jonathan Mee    schedule 02.12.2015    source источник
comment
Что касается пункта 3: нет enable_shared_from_this; есть enable_shared_from_this<D>. Это важно. Если A происходит от enable_shared_from_this<A>, а B происходит от enable_shared_from_this<B>, то это разные базовые классы.   -  person Simple    schedule 02.12.2015
comment
Это называется CRTP.   -  person Simple    schedule 02.12.2015
comment
@Simple Это камень преткновения. На что ссылается shared_from_this? Вы должны указать. Я не утверждаю, что какие-либо из этих трудностей непреодолимы, они просто усложняют код, которого не было бы, если бы не enable_shared_from_this наследования.   -  person Jonathan Mee    schedule 02.12.2015
comment
Я не понимаю, что вы делаете. Пункт 3 WRT не вызывает затруднений (единственный пункт, который я обсуждал), поскольку он не является проблемой.   -  person Simple    schedule 02.12.2015
comment
@Simple Я просто утверждаю, что это добавляет сложности, в дочернем классе больше нельзя вызывать shared_from_this(), теперь они должны знать, вызывать ли A::shared_from_this() или B::shared_from_this(), эта трудность усугубляется, если возвращается shared_ptr. Как я могу внутренне узнать, какой тип shared_ptr вызывающему абоненту нужен shared_ptr<A> или shared_ptr<B>? Опять же, не непреодолимым, это просто добавляет сложности.   -  person Jonathan Mee    schedule 02.12.2015
comment
На практике это не распространенная проблема. Если это проблема, это предполагает, что сложность уже присутствует: вы чрезмерно используете enable_shared_from_this или ваша иерархия классов должна быть изменена, чтобы иметь один базовый класс, который обеспечивает функциональность shared_from_this.   -  person Jonathan Wakely    schedule 02.12.2015
comment
Я думаю, вам следует узнать, как реализованы shared_ptr и enable_shared_from_this, чтобы понять, почему они такие, какие есть. Вы должны иметь возможность построить shared_ptr<T> из уже существующего T; наличие enable_shared_from_this<T> в качестве базового класса означает, что конструктор shared_ptr<T> может хранить weak_ptr<T> внутри T.   -  person Simple    schedule 02.12.2015
comment
Улучшенная спецификация на kayari.org/cxx/enable_shared_from_this.html может помочь понять ее, не читая фактические реализации.   -  person Jonathan Wakely    schedule 02.12.2015
comment
1. Деструктор защищен, поэтому неважно, что он не виртуальный. В любом случае никто не может вызвать его напрямую. 2. Деструктор не уничтожает *this. Оно не может. 3. Если вы видите камень преткновения, укажите, где именно он находится.   -  person n. 1.8e9-where's-my-share m.    schedule 02.12.2015
comment
@Simple Я попытался подчистить вопрос, чтобы лучше понять то, что я пытаюсь сказать. Но, глядя на это, я думаю, что согласен с Комментарий Джонатана Уэйкли Если это проблема, это предполагает, что сложность уже присутствует   -  person Jonathan Mee    schedule 02.12.2015
comment
@н.м. Отличный момент, я сделал эти заметки, на самом деле не пытаясь реализовать класс, когда я пошел, чтобы попробовать его, я понял, что вы совершенно правы, только третья проблема, и для этого есть обходной путь.   -  person Jonathan Mee    schedule 02.12.2015
comment
См. также это. Неограниченное множественное наследование от enable_shared_from_this не работает, но и использование shared_ptr с неограниченным MI тоже не работает, так что ничего ценного не теряется.   -  person n. 1.8e9-where's-my-share m.    schedule 02.12.2015
comment
Re: В свое время меня много раз предупреждали об опасностях наследования от стандартной библиотеки, это слишком широко. Вы должны были быть предупреждены о производных от вещей, которые не предназначены для того, чтобы быть производными. enable_shared_from_this предназначен для получения, и общие опасения по поводу наследования не должны быть фактором при принятии решения о том, использовать ли его так, как он предназначен для использования.   -  person Pete Becker    schedule 02.12.2015
comment
@н.м. Отличный комментарий, полезная ссылка тоже, спасибо, я дал вам случайный +1   -  person Jonathan Mee    schedule 02.12.2015


Ответы (1)


есть ли способ использовать enable_shard_from_this как отношение "имеет" вместо отношения "является"?

No.

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

Даже если бы была веская причина хотеть это сделать (а ее нет), это не сработало бы. Волшебство, которое заставляет базу enable_shared_from_this делить владение с shared_ptr, которому принадлежит производный объект, работает путем проверки наследования.

enable_shared_from_this в любом случае не моделирует отношения IS-A, потому что у него нет интерфейса, определенного в терминах виртуальных функций. IS-A означает производный тип, который расширяет базовый интерфейс, но здесь это не так. А Good ЕСТЬ-НЕ-А enable_shared_from_this<Good>.

т. е. использование наследования не всегда подразумевает отношение IS-A.

  1. enable_shared_from_this не имеет виртуального деструктора

Виртуальный деструктор не имеет значения, если только вы не планируете удалять объект через указатель на базовый класс enable_shared_from_this, что было бы безумием. Нет причин когда-либо передавать Good в качестве указателя на базовый класс enable_shared_from_this<Good>, и еще меньше причин когда-либо использовать delete для этого базового указателя (обычно тип будет сохранен в shared_ptr<Good>, как только он будет создан, так что вы вообще никогда не используйте delete).

enable_shared_from_this — это тип примеси, а не абстрактная база. Он предоставляет shared_from_this (а вскоре и weak_from_this) члена, вот и все. Вы не должны использовать его как абстрактный базовый или тип интерфейса, а также использовать базовый тип для доступа к полиморфному поведению производного типа. Тот факт, что он вообще не имеет виртуальных функций, а не только виртуального деструктора, должен вам об этом сказать.

Кроме того, как н.м. прокомментировано выше, деструктор защищен, поэтому вы не можете удалить его через базовый класс, даже если попытаетесь (защищенный деструктор — это идиоматический способ предотвращения неправильного использования классов примесей, предназначенных для неполиморфные базовые классы).

  1. Деструктор enable_shared_from_this уничтожает *this, то есть он всегда должен вызываться последним деструктором.

Хм? Не уверен, что вы имеете в виду, но он не несет ответственности за уничтожение чего-либо, кроме себя и своего собственного члена данных.

  1. Наследование от двух классов, которые оба наследуют от enable_shared_from_this, может стать камнем преткновения.

Это должно работать нормально (хотя вы можете не получить волшебное совместное владение, если нет ни одного однозначного базового класса, который является специализацией enable_shared_from_this). В стандартной библиотеке GCC была ошибка (сейчас исправлена), из-за которой она не могла скомпилироваться, а не просто не могла разделить владение, но это не проблема с enable_shared_from_this.

person Jonathan Wakely    schedule 02.12.2015