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>