14.3.4. Orden de llamada de constructores y destructores

Es interesante conocer el orden de las llamadas a los constructores y destructores de un objeto con varios subobjetos. El siguiente ejemplo muestra exactamente cómo funciona:

//: C14:Order.cpp
// Constructor/destructor order
#include <fstream>
using namespace std;
ofstream out("order.out");

#define CLASS(ID) class ID { \
public: \
  ID(int) { out << #ID " constructor\n"; } \
  ~ID() { out << #ID " destructor\n"; } \
};

CLASS(Base1);
CLASS(Member1);
CLASS(Member2);
CLASS(Member3);
CLASS(Member4);

class Derived1 : public Base1 {
  Member1 m1;
  Member2 m2;
public:
  Derived1(int) : m2(1), m1(2), Base1(3) {
    out << "Derived1 constructor\n";
  }
  ~Derived1() {
    out << "Derived1 destructor\n";
  }
};

class Derived2 : public Derived1 {
  Member3 m3;
  Member4 m4;
public:
  Derived2() : m3(1), Derived1(2), m4(3) {
    out << "Derived2 constructor\n";
  }
  ~Derived2() {
    out << "Derived2 destructor\n";
  }
};

int main() {
  Derived2 d2;
} ///:~

Listado 14.7. C14/Order.cpp


Primero, se crea un objeto ofstream para enviar toda la salida a un fichero. Después, para ahorrar algo de escritura y mostrar una técnica con macros que se sustituirá por otra mucho mejor en el Capítulo 16, se crea una macro para construir algunas de las clases, que luego se usan en herencia y composición. Cada uno de los constructores y destructores informa de sí mismo en el fichero de traza. Fíjese en que los constructores no son constructores por defecto; cada uno tiene un argumento de tipo int. El argumento no tiene identificador; la única razón de su existencia es obligarle a llamar explícitamente a los constructores en la lista de inicialización. (Al eliminar el identificador se evitan los mensajes de advertencia del compilador.)

La salida de este programa es:

Base1 constructor
Member1 constructor
Member2 constructor
Derived1 constructor
Member3 constructor
Member4 constructor
Derived2 constructor
Derived2 destructor
Member4 destructor
Member3 destructor
Derived1 destructor
Member2 destructor
Member1 destructor
Base1 destructor

Como puede observar, la construcción empieza desde la raíz de la jerarquía de clases y en cada nivel, el constructor de la clase base se ejecuta primero, seguido por los constructores de los objetos miembro. Los destructores se llaman exactamente en el orden inverso al de los constructores; esto es importante debido a las posibles dependencias (en el constructor o en el destructor de la clase derivada, se debe poder asumir que el subobjeto de la clase base sigue disponible para su uso y ya ha sido construido, o todavía no se ha destruido).

También es interesante que al orden de las llamadas a los constructores de los objetos miembro no le afecte en absoluto el orden de las llamadas en la lista de inicialización del constructor. El orden lo determina el orden en que se declaran los objetos miembro en la clase. Si pudiera cambiar el orden de las llamadas a los constructores mediante la lista de inicialización del constructor, podría tener dos secuencias de llamadas distintas en dos constructores diferentes, pero el pobre destructor no sabría cómo invertir correctamente el orden de las llamadas para la destrucción, y podría acabar con un problema de dependencias.