Передача 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.