Преобразование shared_ptr <Base> в shared_ptr <Derived>?

103

Обновление: shared_ptr в этом примере аналогичен таковому в Boost, но он не поддерживает shared_polymorphic_downcast (или dynamic_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);
}

Что ж, это не помогло. Кажется, компилятор полностью проигнорировал мой оператор приведения типов. Есть идеи, как заставить работать задание shared_ptr? За дополнительные баллы: что это за тип Base* const? const Base*Я понимаю, но Base* const? Что имеется в constвиду в этом случае?

Лайош Надь
источник
Почему вам нужен shared_ptr <Derived> вместо shared_ptr <Base>?
Билл
3
Потому что я хочу получить доступ к функциям Derived, которых нет в Base, без клонирования объекта (мне нужен один объект, на который ссылаются два общих указателя). Кстати, почему не работают операторы приведения?
Лайош Надь,

Ответы:

111

Вы можете использовать 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

Также я не рекомендую использовать оператор приведения в базовом классе. Подобное неявное приведение типов может стать источником ошибок и ошибок.

-Update:std::static_pointer_cast может использоваться, если тип не полиморфен .

Массуд Хаари
источник
4
Я не понял из первой строчки, что он не использует std::shared_ptr. Но из комментариев к первому ответу я сделал вывод, что он не использует ускорение, поэтому он может использовать std::shared_ptr.
Massood Khaari
ХОРОШО. Сожалею. Ему следует уточнить, что он использует нестандартную реализацию.
Massood Khaari
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, и вам хотелось бы иметь немного обоих миров. Хорошо известно, что у dynamic_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, не сможет управлять реф-счетом. - В «идеальном» объектно-ориентированном дизайне вы всегда можете использовать базовый тип, и вам никогда не нужно знать и заботиться о том, что такое производный тип, потому что все его функциональные возможности предоставляются через интерфейсы базового класса. Возможно, вам просто нужно переосмыслить, зачем вам вообще нужно понижать цену.
Тим Сильвестр,
1
@ Тим Сильвестр: но, C ++ - не идеальный объектно-ориентированный язык! :-) Унизители нашли свое место в несовершенном ОО-языке
Стив Фолли
4

Если кто-то попадет сюда с boost :: shared_ptr ...

Вот как вы можете выполнить приведение к производному Boost shared_ptr. Предполагая, что Derived наследуется от 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;

Убедитесь, что в базовом классе / структуре есть хотя бы одна виртуальная функция. Также работает виртуальный деструктор.

Митендра
источник