Orden de los constructores / destructores de llamadas en herencia

Una pequeña pregunta sobre la creación de objetos. Digamos que tengo estas dos clases:

struct A{ A(){cout << "A() C-tor" << endl;} ~A(){cout << "~A() D-tor" << endl;} }; struct B : public A{ B(){cout << "B() C-tor" << endl;} ~B(){cout << "~B() D-tor" << endl;} A a; }; 

y en general creo una instancia de B :

 int main(){ B b; } 

Tenga en cuenta que B deriva de A y también tiene un campo de tipo A

Estoy tratando de descubrir las reglas. Sé que al construir un objeto primero llama a su constructor padre, y viceversa cuando se destruye.

¿Qué pasa con los campos ( A a; en este caso)? Cuando se crea B , ¿cuándo llamará al constructor de A? No he definido una lista de inicialización, ¿hay algún tipo de lista predeterminada? Y si no hay una lista predeterminada? Y la misma pregunta sobre la destrucción.

  • La construcción siempre comienza con la class base. Si hay múltiples class básicas entonces, la construcción comienza con la base más a la izquierda. ( nota al margen : si hay una herencia virtual , se le da una preferencia más alta).
  • Entonces los campos de miembros están construidos. Se inicializan en el orden en que se declaran
  • Finalmente, la class sí está construida
  • El orden del destructor es exactamente el reverso

Independientemente de la lista de inicializadores, el orden de las llamadas será así:

  1. Constructor de la class A base class A
  2. se construirá el campo de class B llamado a (de tipo class A )
  3. Constructor derivado de la class B

Suponiendo que no hay herencia virtual / múltiple (eso complica bastante las cosas), las reglas son simples:

  1. La memoria de objeto está asignada
  2. El constructor de las clases base se ejecuta, terminando con la mayoría de los derivados
  3. La inicialización del miembro se ejecuta
  4. El objeto se convierte en una verdadera instancia de su clase
  5. El código de constructor se ejecuta

Una cosa importante para recordar es que hasta el paso 4 el objeto aún no es una instancia de su clase, ya que gana este título solo después de que comienza la ejecución del constructor. Esto significa que si se lanza una excepción durante el constructor de un miembro, el destructor del objeto no se ejecutará, sino que solo se destruirán las partes ya construidas (por ejemplo, miembros o clases base). Esto también significa que si en el constructor de un miembro o de una clase base llama a cualquier función de miembro virtual del objeto al que llama la implementación será la base, no la derivada. Otra cosa importante para recordar es que los miembros enumerados en la lista de inicialización se construirán en el orden en que se declaran en la clase, NO en el orden en que aparecen en la lista de inicialización (afortunadamente, la mayoría de los comstackdores decentes emitirán una advertencia si enumera miembros en un orden diferente de la statement de clase).

Nótese también que incluso si durante la ejecución del código del constructor this objeto ya obtuvo su clase final (por ejemplo, respecto al despacho virtual) NO se va a llamar al destructor de la clase a menos que el constructor complete su ejecución . Solo cuando el constructor completa la ejecución, la instancia del objeto es un verdadero ciudadano de primera clase entre las instancias … antes de ese punto es solo una “instancia aspirante” (a pesar de tener la clase correcta).

La destrucción ocurre en el orden inverso exacto: primero se ejecuta el destructor de objeto, luego pierde su clase (es decir, a partir de este momento el objeto se considera un objeto base), todos los miembros se destruyen en orden de statement inversa y finalmente el proceso de destrucción de clase base se ejecuta hasta el padre más abstracto. En cuanto al constructor, si llama a cualquier función de miembro virtual del objeto (directa o indirectamente) en un destructor base o miembro, la implementación ejecutada será la principal debido a que el objeto perdió su título de clase cuando se completó el destructor de clase.

Las clases base siempre se construyen antes de los miembros de datos. Los miembros de datos se construyen en el orden en que se declaran en la clase. Esta orden no tiene nada que ver con la lista de inicialización. Cuando se inicializa un miembro de datos, buscará los parámetros en su lista de inicialización y llamará al constructor predeterminado si no hay coincidencia. Los descriptores para los miembros de datos siempre se llaman en el orden inverso.

El constructor de la clase base siempre ejecuta first.so cuando se escribe una instrucción B b; el constructor de A se llama primero y luego el constructor de clase B , la salida de los constructores estará en una secuencia de la siguiente manera:

 A() C-tor A() C-tor B() C-tor 
 #include class A { public: A(int n=2): m_i(n) { // std::cout<<"Base Constructed with m_i "< 

La respuesta en este caso es 2531. Cómo se llaman aquí los constructores:

  1. Se llama al constructor B :: A (int n = 2)
  2. B :: B (5) constructor se llama
  3. B.m_A1 :: A (3) se llama
  4. B.m_A2 :: A (5) se llama

De la misma manera Destructor se llama:

  1. B :: ~ B () se llama. es decir m_i = 2, que decrementa m_i a 1 en A.
  2. B.m_A2 :: ~ A () se llama. m_i = 5
  3. B.m_A1 :: ~ A () se llama. m_i = 3 4 B :: ~ A () se llama., m_i = 1

En este ejemplo, la construcción de m_A1 & m_A2 es irrelevante para el orden del orden de la lista de inicialización, pero su orden de statement.

La salida del código modificado es:

 A() C-tor A() C-tor B() C-tor ~B() D-tor ~A() D-tor ~A() D-tor