Може ли 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
Не разбирам какво намекваш. Няма трудност WRT точка 3 (единствената точка, която съм обсъждал), тъй като не е проблем.   -  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
@n.m. Чудесно, направих тези бележки, без всъщност да се опитвам да внедря класа, когато отидох да го изпробвам, разбрах, че си напълно прав, само третото е проблем и има работа за това.   -  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
@n.m. Страхотен коментар, полезна връзка също, благодаря, дадох ви случаен +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 означава производен тип, който разширява базов интерфейс, но това не е случаят тук. A Good Е-НЕ-A 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