new how delete c++ constructor this multiple-inheritance dynamic-cast

how - destroy object c++



dynamic_cast de "this" dentro de constructor (5)

Cada constructor de clase base se ejecuta antes que el constructor de clase derivado, y durante el constructor B , el tipo dinámico del objeto es B ; no se convierte en C hasta que ingrese al constructor C Por lo tanto, no puede hacer nada que requiera un tipo dinámico de C : no puede realizar el cross-cast a ninguna de las otras clases base de C , y si llama a una función virtual, entonces no obtendrá ninguna anulación provista por C

Bajo el capó, el tipo dinámico es (en la mayoría de las implementaciones) determinado por un puntero en el objeto (conocido como "vptr"), que apunta a algunas propiedades de especificación de datos estáticos de la clase, incluyendo una tabla de funciones virtuales ( conocido como "vtable") y la información necesaria para dynamic_cast y typeid . Antes de cada constructor, esto se actualiza para señalar la información de la clase actualmente en construcción.

Esta pregunta es muy similar a esta ¿Por qué no puedo dynamic_cast "hacia los lados" durante la herencia múltiple? , excepto que el reparto funciona, pero no dentro del constructor.

Encabezamiento:

class A { public: virtual ~A() {} void printA(); }; class B { public: B(); virtual ~B() {} void printB(); private: std::string message_; }; class C : public A, public B { public: C() {} virtual ~C() {} };

Fuente:

void A::printA() { cout << "A" << endl; } B::B() { A* a = dynamic_cast< A* >( this ); if ( a ) { message_ = std::string( "A and B" ); } else { message_ = std::string( "B" ); } } void B::printB() { cout << message_.c_str() << endl; }

Principal:

int main( int argc, char* argv[] ) { cout << "Printing C..." << endl; C c; c.printA(); c.printB(); cout << "Checking again..." << endl; cout << !!dynamic_cast< A* >( &c ) << endl; return EXIT_SUCCESS; }

Resultado:

Printing C... A B Checking again... 1

Entonces, el dynamic_cast funciona para la herencia múltiple (¡no hay sorpresas allí!), Pero ¿por qué no cuando se llama en el tiempo de ejecución para el puntero ''this'' dentro de B :: B ()? Pensé que el objeto estaba completamente formado una vez dentro del cuerpo del constructor, es decir, toda la memoria se asignó a los objetos componentes, aún no se han inicializado. Aprecio que esto dependa del orden del constructor de la superclase, pero en este ejemplo A se llama antes que B.

Obviamente no estoy entendiendo qué está sucediendo exactamente bajo el capó, ¿puede alguien por favor iluminarme?

Gracias, Cam Bamber.


Como B no hereda de A ( B es la clase padre), el tipo dinámico de B durante su constructor es B Solo cuando se construyen los padres A y B se puede construir el niño C , lo que permite la dynamic_cast hacia los dynamic_cast .


Durante la construcción de A entonces el tipo dinámico es A independientemente. Esto se debe a que comenzaría a llamar a funciones miembro de clases derivadas y acceder a variables miembro derivadas antes de que se haya construido, lo que sería UB y muy malo.


No funciona dentro de B, porque B no hereda de A


Básicamente, el estándar dice que no funcionará (dynamic_cast) durante la construcción de un objeto. <cita>

Editar: Agregado basado en el comentario de VJo a continuación.

Nota: El lanzamiento de una ''B'' a una ''A'' usando un lanzamiento dinámico debería funcionar porque estamos lanzando un objeto de tipo ''C''. Si agregamos el siguiente código a main:

B bObj; B& bRef = c; B* bPtr = &c; std::cout << !!dynamic_cast<A*>(&bObj) << std::endl; std::cout << !!dynamic_cast<A*>(&bRef) << std::endl; std::cout << !!dynamic_cast<A*>( bPtr) << std::endl;

El resultado extra sería:

0 // Can not convert a B to an A 1 // Can convert this B to an A because it is really a C. 1 // This is what we are reeling doing in B::B() that fails // It is not the dynamic_cast<> that fails but the conversion of this from C* to B* // That is causing UB

Falla en el constructor porque el objeto no está completamente formado. Usando esto estamos tratando de convertir un puntero C en un puntero B antes de que el constructor C haya comenzado (el código definido por el usuario). Por lo tanto, el uso de this en B :: B () como un puntero a un objeto C falla así cuando se invoca el dynamic_cast <> porque no puede hacer lo que usted quiere debido a UB.

12.7 Construcción y destrucción [class.cdtor]

Párrafo 3

Convertir explícita o implícitamente un puntero (un glvalue) refiriéndose a un objeto de clase X a un puntero (referencia) a una clase base directa o indirecta B de X, la construcción de X y la construcción de todas sus bases directas o indirectas que directa o indirectamente se derivan de B habrá comenzado y la destrucción de estas clases no se habrá completado; de lo contrario, la conversión dará como resultado un comportamiento indefinido. Para formar un puntero a (o acceder al valor de) un miembro directo no estático de un objeto obj, la construcción de obj debe haber comenzado y su destrucción no debe haberse completado; de lo contrario, el cálculo del valor del puntero (o el acceso al miembro valor) da como resultado un comportamiento indefinido.

[Ejemplo:

struct A { }; struct B : virtual A { }; struct C : B { }; struct D : virtual A { D(A*); }; struct X { X(A*); }; struct E : C, D, X { E() : D(this), // undefined: upcast from E* to A* // might use path E* → D* → A* // but D is not constructed // D((C*)this), // defined: // E* → C* defined because E() has started // and C* → A* defined because // C fully constructed X(this) { // defined: upon construction of X, // C/B/D/A sublattice is fully constructed } };

- ejemplo final]

</ quote>