c++ smart-pointers

c++ - ¿Está bien derivar de std:: enable_shared_from_this y una clase base abstracta?



smart-pointers (2)

Estoy escribiendo una clase que debería derivar de una clase base abstracta. No puedo cambiar la clase base abstracta. La clase se mantendrá como shared_ptr para la clase base abstracta. ¿Está bien heredar de la clase base abstracta y enable_shared_from_this ? Me gusta esto:

class IWidget { public: virtual ~IWidget(){} // ... }; class Widget : public std::enable_shared_from_this<Widget>, public IWidget { protected: Widget(); // protected, use create public: static std::shared_ptr<IWidget> create() { return std::shared_ptr<IWidget>(new Widget(init)); } // ... };

Código más completo here que parece funcionar.

La mayoría de los ejemplos que puedo encontrar de enable_shared_from_this tienen en la clase base. En este caso no puedo cambiar la clase base. ¿Está bien usar herencia múltiple y usarla en la clase derivada?

Estaba un poco preocupado de que solo pudiera garantizar que enable_shared_from_this solo funcionaría si creé un shared_ptr<Widget> pero en este caso estoy creando un shared_ptr<IWidget> .

Actualización: una cosa interesante que noté es que si cambio el método de create a:

IWidget* w = new Widget(init); return std::shared_ptr<IWidget>(w);

Recibo un error de tiempo de ejecución cuando trato de usar shared_from_this() . Creo que esto tiene sentido. shared_ptr tiene un constructor con plantilla que toma un puntero "convertible". Y a menos que el constructor shared_ptr sepa que está tomando un Widget , no sabe que se deriva de enable_shared_from_this y no puede almacenar un weak_ptr . Me pregunto si este comportamiento está documentado.


Sí, está absolutamente bien.

shared_ptr tiene un constructor con plantilla que toma un puntero "convertible". Y a menos que el constructor shared_ptr sepa que está tomando un Widget , no sabe que se deriva de enable_shared_from_this y no puede almacenar un weak_ptr .

Exactamente correcto.

Me pregunto si este comportamiento está documentado.

En el estándar actual, enable_shared_from_this está muy mal especificado, pero vea P0033 para la nueva y mejorada especificación de enable_shared_from_this que estará en C ++ 17. Además de contestar "¿qué pasa si lo haces dos veces?" En la pregunta, la redacción revisada especifica exactamente cómo se utiliza la clase base enable_shared_from_this , y cómo se inicializa el miembro weak_ptr . Esa parte de la nueva redacción es simplemente estandarizar la práctica existente, es decir, es simplemente una descripción más precisa de lo que ya hacen las implementaciones reales. (La respuesta a la pregunta "¿qué sucede si lo haces dos veces?" Se desvía de lo que las implementaciones hicieron anteriormente, pero por buenas razones, y eso no es relevante para tu pregunta de todos modos).

La nueva especificación aclara que su ejemplo original está completamente bien definido y es correcto.

El estándar actual dice que la versión modificada en tu pregunta actualizada tiene un comportamiento indefinido cuando llamas a shared_from_this() , debido a que se viola la condición previa de que shared_ptr dueño del puntero a derivado (porque creas un shared_ptr que posee el puntero a la base). Sin embargo, esa condición previa no es suficiente para garantizar una semántica sensible, como se explica en el documento. La redacción revisada hace que su versión modificada también weak_ptr bien definida (es decir, no weak_ptr un comportamiento indefinido), pero la weak_ptr de la clase base no compartirá la propiedad con el shared_ptr<IWidget> y, por shared_from_this() tanto, shared_from_this() lanzará una excepción (que es lo que observa desde su implementación).


Sí. Estará bien, siempre y cuando nadie confíe en un comportamiento indefinido. Ahora, estrictamente hablando, su código ya está roto si usa UB, pero en la práctica la herencia múltiple hace que muchas de las suposiciones no válidas que las personas hacen más obvias.

Estoy pensando particularmente en

Base* p = this; Derived* pDerived = reinterpret_cast<Derived*>(p);

Puede que no funcione como se espera. Necesita ser static_cast .