Downcasting shared_ptr <Base> a shared_ptr <Derived>?

102

Actualización: el shared_ptr en este ejemplo es como el de Boost, pero no es compatible con shared_polymorphic_downcast (o dynamic_pointer_cast o static_pointer_cast para el caso).

Estoy tratando de inicializar un puntero compartido a una clase derivada sin perder el recuento de referencias:

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;  

Hasta aquí todo bien. No esperaba que C ++ convirtiera implícitamente Base * a Derived *. Sin embargo, quiero la funcionalidad expresada por el código (es decir, mantener el recuento de referencias mientras se reduce el puntero base). Mi primer pensamiento fue proporcionar un operador de conversión en Base para que pudiera tener lugar una conversión implícita a Derivado (para los pedantes: comprobaría que la conversión hacia abajo es válida, no se preocupe):

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

Bueno, no ayudó. Parece que el compilador ignoró completamente mi operador encasillado. ¿Alguna idea de cómo podría hacer que funcione la asignación shared_ptr? Para puntos extra: ¿qué tipo Base* constes? const Base*Entiendo, pero Base* const? ¿A qué se constrefiere en este caso?

Lajos Nagy
fuente
¿Por qué necesita un shared_ptr <Derived>, en lugar de shared_ptr <Base>?
Bill
3
Porque quiero acceder a la funcionalidad en Derived que no está en Base, sin clonar el objeto (quiero un solo objeto, referenciado por dos punteros compartidos). Por cierto, ¿por qué no funcionan los operadores de reparto?
Lajos Nagy

Respuestas:

109

Puede utilizar dynamic_pointer_cast. Es apoyado por std::shared_ptr.

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

Documentación: https://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast

Además, no recomiendo usar el operador de conversión en la clase base. La transmisión implícita como esta puede convertirse en la fuente de errores y errores.

-Actualización: si el tipo no es polimórfico, std::static_pointer_castse puede utilizar.

Massood Khaari
fuente
4
No entendí desde la primera línea que no está usando std::shared_ptr. Pero de los comentarios de la primera respuesta deduje que no está usando boost, por lo que puede estar usando std::shared_ptr.
Massood Khaari
OKAY. Lo siento. Debería aclarar mejor que está usando una implementación personalizada.
Massood Khaari
47

Supongo que estás usando boost::shared_ptr... Creo que quieres dynamic_pointer_casto shared_polymorphic_downcast.

Sin embargo, estos requieren tipos polimórficos.

¿Qué tipo de tipo Base* constes? const Base*Entiendo, pero Base* const? ¿A qué se constrefiere en este caso?

  • const Base *es un puntero mutable a una constante Base.
  • Base const *es un puntero mutable a una constante Base.
  • Base * constes un puntero constante a un mutable Base.
  • Base const * constes un puntero constante a una constante Base.

Aquí tienes un ejemplo mínimo:

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);

No estoy seguro de si fue intencional que su ejemplo creara una instancia del tipo base y la lanzara, pero sirve para ilustrar la diferencia muy bien.

La static_pointer_castvoluntad "simplemente hazlo". Esto dará como resultado un comportamiento indefinido (un Derived*apuntar a la memoria asignada e inicializada por Base) y probablemente causará un bloqueo, o algo peor. Se baseincrementará el recuento de referencias .

El dynamic_pointer_castresultará en un puntero nulo. El recuento de referencias baseno se modificará.

El shared_polymorphic_downcasttendrá el mismo resultado que un molde estático, sino que desencadenará una afirmación, más que aparente tener éxito y que conduce a un comportamiento indefinido. Se baseincrementará el recuento de referencias .

Ver (enlace muerto) :

A veces es un poco difícil decidir si usar static_casto dynamic_cast, y desearía poder tener un poco de ambos mundos. Es bien sabido que dynamic_cast tiene una sobrecarga de tiempo de ejecución, pero es más seguro, mientras que static_cast no tiene ninguna sobrecarga, pero puede fallar silenciosamente. Qué bueno sería si pudiera usarlo shared_dynamic_casten compilaciones de depuración y shared_static_casten compilaciones de lanzamiento. Bueno, tal cosa ya está disponible y se llama shared_polymorphic_downcast.

Tim Sylvester
fuente
Desafortunadamente, su solución depende de la funcionalidad de Boost que se excluyó deliberadamente de la implementación de shared_ptr particular que estamos usando (no pregunte por qué). En cuanto a la explicación constante, ahora tiene mucho más sentido.
Lajos Nagy
3
Aparte de implementar los otros shared_ptrconstructores (tomando static_cast_tagy dynamic_cast_tag), no hay mucho que pueda hacer. Cualquier cosa que hagas fuera shared_ptrno podrá gestionar el recuento. - En un diseño OO "perfecto" siempre se puede utilizar el tipo base, y nunca necesitará saber ni preocuparse cuál es el tipo derivado, porque toda su funcionalidad se expone a través de interfaces de clase base. Quizás solo necesites repensar por qué necesitas rebajar en primer lugar.
Tim Sylvester
1
@Tim Sylvester: ¡pero C ++ no es un lenguaje OO "perfecto"! :-) los down-casts tienen su lugar en un lenguaje OO no perfecto
Steve Folly
4

Si alguien llega aquí con boost :: shared_ptr ...

Así es como puede bajar al Boost shared_ptr derivado. Suponiendo que Derived hereda de 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;

Asegúrese de que la clase / estructura 'Base' tenga al menos una función virtual. Un destructor virtual también funciona.

Mitendra
fuente