Передача спільних_ptr <Base> на shared_ptr <Похідне>?


102

Оновлення: shared_ptr у цьому прикладі схожий на той, що в Boost, але він не підтримує shared_polymorphic_downcast (або динамічний_pointer_cast або static_pointer_cast для цього питання)!

Я намагаюся ініціалізувати спільний покажчик до похідного класу, не втрачаючи підрахунку:

struct Base { };
struct Derived : public Base { };
shared_ptr<Base> base(new Base());
shared_ptr<Derived> derived;

// error: invalid conversion from 'Base* const' to 'Derived*'
derived = base;  

Все йде нормально. Я не очікував, що C ++ неявно перетворить Base * в Derived *. Однак я хочу, щоб функціонал, виражений кодом (тобто, підтримка підрахунку посилань при зниженні базового покажчика). Моя перша думка полягала в тому, щоб надати Base-оператора для того, щоб відбулося неявне перетворення на Derived (для педантів: я би перевірив, чи є нижчий склад правильним, не хвилюйтеся):

struct Base {
  operator Derived* ();
}
// ...
Base::operator Derived* () {
  return down_cast<Derived*>(this);
}

Ну, це не допомогло. Здається, компілятор повністю проігнорував мого оператора typecast. Будь-які ідеї, як я можу зробити так, щоб завдання на спільне використання_ptr працювали? За додаткові бали: що це за тип Base* const? const Base*Я розумію, але Base* const? На що constйдеться у цьому випадку?


Для чого вам потрібен shared_ptr <Derived>, а не shared_ptr <Base>?
Білл

3
Тому що я хочу отримати доступ до функціональності в Derived, який не знаходиться в Base, без клонування об'єкта (я хочу, щоб один об’єкт, на який посилаються два загальних вказівника). До речі, чому оператори литої роботи не працюють?
Lajos Nagy

Відповіді:


109

Можна використовувати dynamic_pointer_cast. Його підтримує std::shared_ptr.

std::shared_ptr<Base> base (new Derived());
std::shared_ptr<Derived> derived =
               std::dynamic_pointer_cast<Derived> (base);

Документація: https://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast

Також я не рекомендую використовувати оператор лиття в базовому класі. Неявне кастинг на зразок цього може стати джерелом помилок та помилок.

-Оновлення: Якщо тип не є поліморфним, std::static_pointer_castйого можна використовувати.


4
Я з першого рядка не зрозумів, що він не використовує std::shared_ptr. Але з коментарів першої відповіді я зробив висновок, що він не використовує прискорення, тому він може використовувати std::shared_ptr.
Массуд Хаарі

ГАРАЗД. Вибачте. Йому слід краще уточнити, що він використовує власну реалізацію.
Массуд Хаарі

47

Я припускаю, що ви використовуєте boost::shared_ptr... я думаю, ви хочете dynamic_pointer_castабо shared_polymorphic_downcast.

Однак для цього потрібні поліморфні типи.

що це за тип Base* const? const Base*Я розумію, але Base* const? На що constйдеться у цьому випадку?

  • const Base *є змінним покажчиком на константу Base.
  • Base const *є змінним покажчиком на константу Base.
  • Base * constє постійним вказівником на змінний Base.
  • Base const * constє постійним покажчиком на константу Base.

Ось мінімальний приклад:

struct Base { virtual ~Base() { } };   // dynamic casts require polymorphic types
struct Derived : public Base { };

boost::shared_ptr<Base> base(new Base());
boost::shared_ptr<Derived> derived;
derived = boost::static_pointer_cast<Derived>(base);
derived = boost::dynamic_pointer_cast<Derived>(base);
derived = boost::shared_polymorphic_downcast<Derived>(base);

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

static_pointer_castБуде «просто зробити це». Це призведе до невизначеної поведінки ( Derived*вказівка ​​на пам'ять, виділену і ініціалізовану Base) і, ймовірно, спричинить збій або ще гірше. Довідковий рахунок baseбуде збільшуватися.

Результат dynamic_pointer_castотримає нульовий покажчик. Довідковий розрахунок baseбуде незмінним.

shared_polymorphic_downcastМатиме той же результат , як статичний кидок, але викличе твердження, а не удаваний успіх і призводить до невизначеного поведінки. Довідковий рахунок baseбуде збільшуватися.

Дивіться (мертве посилання) :

Іноді важко вирішити, використовувати static_castчи dynamic_cast, і ти хочеш, щоб ти мав трохи обох світів. Добре відомо, що динамичний_cast має накладні витрати, але це безпечніше, тоді як static_cast взагалі не має накладних витрат, але він може вийти з ладу. Як би добре, якби ви могли використовувати shared_dynamic_castв налагодженнях і shared_static_castв версіях версій. Ну, така річ вже є і називається shared_polymorphic_downcast.


На жаль, ваше рішення залежить від функціоналу Boost, який був навмисно виключений із конкретної реалізації shared_ptr, яку ми використовуємо (не питайте, чому). Що стосується пояснення const, то зараз воно має набагато більше сенсу.
Лайош Надь

3
За винятком реалізації інших shared_ptrконструкторів (прийняття static_cast_tagта dynamic_cast_tag), ви не можете багато чого зробити. Все, що ви робите поза shared_ptr, не зможе керувати знижкою. - У "ідеальному" OO-дизайні ви завжди можете використовувати базовий тип, і ніколи не потрібно знати і не цікавити, що це похідний тип, тому що вся його функціональність відкрита через інтерфейси базового класу. Можливо, вам просто потрібно заново подумати, чому вам потрібно в першу чергу перемогти.
Тім Сильвестр

1
@Tim Sylvester: але, C ++ не є "ідеальною" мовою ОО! :-) down-casts мають своє місце в не ідеальній мові ОО
Стів Фоллі

4

Якщо хтось потрапляє сюди з boost :: shared_ptr ...

Це те, як ви можете перейти до похідного Boost shared_ptr. Припускаючи похідні спадщини від Base.

boost::shared_ptr<Base> bS;
bS.reset(new Derived());

boost::shared_ptr<Derived> dS = boost::dynamic_pointer_cast<Derived,Base>(bS);
std::cout << "DerivedSPtr  is: " << std::boolalpha << (dS.get() != 0) << std::endl;

Переконайтесь, що клас / структура "Base" має принаймні одну віртуальну функцію. Працює і віртуальний деструктор.

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