Передача shared_ptr <Derived> як shared_ptr <Base>


93

Який найкращий метод використовувати передачу shared_ptrпохідного типу функції, яка приймає shared_ptra базового типу?

Я зазвичай передаю shared_ptrs за посиланням, щоб уникнути непотрібної копії:

int foo(const shared_ptr<bar>& ptr);

але це не працює, якщо я намагаюся зробити щось подібне

int foo(const shared_ptr<Base>& ptr);

...

shared_ptr<Derived> bar = make_shared<Derived>();
foo(bar);

Я міг би використати

foo(dynamic_pointer_cast<Base, Derived>(bar));

але це здається неоптимальним з двох причин:

  • А dynamic_castздається трохи надмірним для простого похідного акторського складу.
  • Як я розумію, dynamic_pointer_castстворює копію (хоча і тимчасову) вказівника для передачі функції.

Чи є краще рішення?

Оновлення для нащадків:

Це виявилося проблемою відсутнього файлу заголовка. Крім того, те, що я намагався зробити тут, вважається антиобразцем. Як правило,

  • Функції , які не впливають життя об'єкта (тобто об'єкт залишається в силі в протягом терміну дії функції) повинні взяти звичайну посилання або покажчик, наприклад int foo(bar& b).

  • Функції, які споживають об'єкт (тобто є кінцевими користувачами даного об'єкта), повинні приймати unique_ptrзначення by, наприклад int foo(unique_ptr<bar> b). Викличники повинні ввести std::moveзначення у функцію.

  • Функції, що продовжують час життя об'єкта, повинні приймати shared_ptrзначення за значенням, наприклад int foo(shared_ptr<bar> b). Застосовується звичайна порада уникати циркулярних посилань .

Детальніше див. У розмові Herb Sutter « Назад до основ» .


8
Чому ви хочете скласти shared_ptr? Чому немає констант-посилання на бар?
— ipc

2
Будь-який dynamicакторський склад потрібен лише для зниження. Крім того, передача похідного вказівника повинна працювати нормально. Він створить новий shared_ptrіз тим самим перерахунком (і збільшить його) та вказівником на базу, який потім прив'язується до посилання const. Оскільки ви вже берете посилання, я не розумію, чому ви взагалі хочете взяти shared_ptr. Візьміть Base const&і зателефонуйте foo(*bar).
— Xeo

@Xeo: Передача похідного вказівника (тобто foo(bar)) не працює, принаймні в MSVC 2010.
— Метт Клайн

1
Що ви маєте на увазі під "очевидно, не працює"? Код компілюється і поводиться коректно; Ви запитуєте, як уникнути створення тимчасового shared_ptrпереходу до функції? Я впевнений, що цього неможливо уникнути.
— Mike Seymour

1
@ Сет: Я не згоден. Я думаю, що є причина передавати спільний вказівник за значенням, і дуже мало причин передавати загальний вказівник за посиланням (і все це, не захищаючи непотрібні копії). Міркування тут stackoverflow.com/questions/10826541/…
— Р. Мартіньо Фернандес

Відповіді:


47

Хоча Baseі Derivedє коваріантними, і необроблені вказівники на них діятимуть відповідно, shared_ptr<Base>і неshared_ptr<Derived> є коваріантними. Це правильний і найпростіший спосіб вирішення цієї проблеми.dynamic_pointer_cast

( Редагувати: static_pointer_cast було б доречніше, оскільки ви проводите кастинг з похідного на базу, що безпечно і не вимагає перевірки часу виконання. Див. Коментарі нижче.)

Однак, якщо ваша foo()функція не бажає брати участь у продовженні терміну служби (або, вірніше, брати участь у спільному володінні об'єктом), тоді найкраще прийняти a const Base&та розмежувати його shared_ptrпри передачі foo().

void foo(const Base& base);
[...]
shared_ptr<Derived> spDerived = getDerived();
foo(*spDerived);

Окрім того, оскільки shared_ptrтипи не можуть бути коваріантними, правила неявних перетворень між коваріантними типами повернення не застосовуються при поверненні типів shared_ptr<T>.


39
Вони не коваріантні, але shared_ptr<Derived>неявно конвертовані в shared_ptr<Base>, тому код повинен працювати без будь-яких хитрощів.
— Mike Seymour

9
Гм, shared_ptr<Ty>має конструктор, який приймає shared_ptr<Other>та виконує відповідне перетворення, якщо Ty*неявно конвертоване в Other*. І якщо потрібен акторський static_pointer_castсклад, то відповідний тут, ні dynamic_pointer_cast.
— Піт Беккер,

Правда, але не з його контрольним параметром, як у питанні. Йому потрібно було б зробити копію, незалежно. Але, якщо він використовує посилання на shared_ptr', щоб уникнути підрахунку посилань, тоді насправді немає вагомих причин використовувати shared_ptrспочатку a . Краще використовувати const Base&замість цього.
— Брет Кунс,

@PeteBecker Перегляньте мій коментар Майку про конструктор перетворення. Я, чесно кажучи, не знав про це static_pointer_cast, дякую.
— Bret Kuhns

1
@TanveerBadar Не впевнений. Можливо, цього не вдалося скласти у 2012 році? (зокрема, за допомогою Visual Studio 2010 або 2012). Але ви абсолютно праві, код OP повинен абсолютно скомпілювати, якщо повне визначення a / публічно виведеного / класу видно компілятору.
— Брет Кунс

32

Це також трапиться, якщо ви забули вказати загальнодоступне успадкування для похідного класу, тобто якщо, як я, ви напишете це:

class Derived : Base
{
};

classдля параметрів шаблону; structпризначений для визначення класів. (Це щонайбільше 45% жарт.)
— Девіс Оселедець

Це, безумовно, слід вважати рішенням, немає необхідності у складі акторського складу, оскільки його бракує лише публіці.
— Алексіс Пакес,

12

Здається, ви занадто стараєтесь. shared_ptrкопіювати дешево; це одна з його цілей. Передача їх за допомогою посилання насправді не дає багато чого. Якщо ви не хочете ділитися, передайте сирий вказівник.

Тим не менш, є два способи зробити це, про що я можу подумати з маківки:

foo(shared_ptr<Base>(bar));
foo(static_pointer_cast<Base>(bar));

9
Ні, їх не дешево копіювати, їх слід передавати за посиланням, коли це можливо.
— Сет Карнегі,

6
@SethCarnegie - Херб профілював ваш код, щоб побачити, чи проходження мимо значення є вузьким місцем?
— Піт Беккер,

25
@SethCarnegie - це не відповідає на запитання, яке я задав. І, що того варте, я написав shared_ptrреалізацію, яку постачає Microsoft.
— Піт Беккер,

6
@SethCarnegie - у вас евристика назад. Як правило, оптимізацію рук не слід робити, якщо ви не можете показати, що вони потрібні.
— Піт Беккер,

21
Це лише «передчасна» оптимізація, якщо вам потрібно над цим працювати. Я не бачу жодної проблеми в прийнятті ефективних ідіом над неефективними, незалежно від того, чи це впливає на конкретний контекст чи ні.
— Марк Ренсом

11

Також перевірте, чи #includeфайл заголовка, що містить повну декларацію похідного класу, знаходиться у вашому вихідному файлі.

У мене була ця проблема. Не std::shared<derived>хотів би кинути std::shared<base>. Я попередньо оголосив обидва класи, щоб міг тримати вказівники на них, але оскільки у мене не було #includeкомпілятора, я не міг бачити, що один клас походить від іншого.


1
Ого, я цього не очікував, але це мені це виправило. Я був дуже обережним і включав файли заголовків лише там, де вони мені були потрібні, тому деякі з них були лише у вихідних файлах і вперед оголошували їх у заголовки, як ви сказали.
— jigglypuff

Дурний компілятор - дурний. Це було моє питання. Дякую!
— Танвер Бадар
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.