Constructores de clases derivadas

Cuando se crea un objeto de una clase derivada, primero se invoca al constructor de la clase o clases base y a continuación al constructor de la clase derivada. Si la clase base es a su vez una clase derivada, el proceso se repite recursivamente.

Lógicamente, si no hemos definido los constructores de las clases, se usan los constructores por defecto que crea el compilador.

Veamos un ejemplo:

#include <iostream>
using namespace std;

class ClaseA {
  public:
   ClaseA() : datoA(10) { 
      cout << "Constructor de A" << endl; 
   } 
   int LeerA() const { return datoA; }
   
  protected:
   int datoA;
};

class ClaseB : public ClaseA {
  public:
   ClaseB() : datoB(20) { 
      cout << "Constructor de B" << endl; 
   }
   int LeerB() const { return datoB; }

  protected:
   int datoB;
};

int main() {
   ClaseB objeto;
   
   cout << "a = " << objeto.LeerA() 
        << ", b = " << objeto.LeerB() << endl;
   
   return 0;
}

La salida es ésta:

Constructor de A
Constructor de B
a = 10, b = 20

Se ve claramente que primero se llama al constructor de la clase base A, y después al de la clase derivada B.

Es relativamente fácil comprender esto cuando usamos constructores por defecto o cuando nuestros constructores no tienen parámetros, pero cuando sobrecargamos los constructores o usamos constructores con parámetros, no es tan simple.

Inicialización de clases base en constructores

Cuando queramos inicializar las clases base usando parámetros desde el constructor de una clase derivada lo haremos de modo análogo a como lo hacemos con los datos miembro, usaremos el constructor de la clase base con los parámetros adecuados. Las llamadas a los constructores deben escribirse antes de las inicializaciones de los parámetros.

Sintaxis:

<clase_derivada>(<lista_de_parámetros>) : 
   <clase_base>(<lista_de_parámetros>) {}

De nuevo lo veremos mejor con otro ejemplo:

#include <iostream>
using namespace std;

class ClaseA {
  public:
   ClaseA(int a) : datoA(a) { 
      cout << "Constructor de A" << endl; 
   }
   int LeerA() const { return datoA; }
   
  protected:
   int datoA;
};

class ClaseB : public ClaseA {
  public:
   ClaseB(int a, int b) : ClaseA(a), datoB(b) { // (1)
      cout << "Constructor de B" << endl; 
   }
   int LeerB() const { return datoB; }

  protected:
   int datoB;
};

int main() {
   ClaseB objeto(5,15);
   
   cout << "a = " << objeto.LeerA() << ", b = " 
        << objeto.LeerB() << endl;
   
   return 0;
}

La salida es esta:

Constructor de A
Constructor de B
a = 5, b = 15

Observa cómo hemos definido el constructor de la ClaseB (1). Para empezar, recibe dos parámetros: "a" y "b". El primero se usará para inicializar la clase base ClaseA, para ello, después de los dos puntos, escribimos el constructor de la ClaseA, y usamos como parámetro el valor "a". A continuación escribimos la inicialización del datoB, separado con una coma y usamos el valor "b".

Inicialización de objetos miembros de clases

También es posible que una clase tenga como miembros objetos de otras clases, en ese caso, para inicializar esos miembros se procede del mismo modo que con cualquier dato miembro, es decir, se añade el nombre del objeto junto con sus parámetros a la lista de inicializaciones del constructor.

Esto es válido tanto en clases base como en clases derivadas.

Veamos un ejemplo:

#include <iostream>
using namespace std;

class ClaseA {
  public:
   ClaseA(int a) : datoA(a) { 
      cout << "Constructor de A" << endl; 
   }
   int LeerA() const { return datoA; }
   
  protected:
   int datoA;
};

class ClaseB {
  public:
   ClaseB(int a, int b) : cA(a), datoB(b) { // (1)
      cout << "Constructor de B" << endl; 
   } 
   int LeerB() const { return datoB; }
   int LeerA() const { return cA.LeerA(); } // (2)

  protected:
   int datoB;
   ClaseA cA;
};

int main() {
   ClaseB objeto(5,15);
   
   cout << "a = " << objeto.LeerA() << ", b = " 
        << objeto.LeerB() << endl;
   
   return 0;
}

En la línea (1) se ve cómo inicializamos el objeto de la ClaseA (cA), que hemos incluido dentro de la ClaseB.

En la línea (2) vemos que hemos tenido que añadir una nueva función para que sea posible acceder a los datos del objeto cA. Si hubiéramos declarado cA como public, este paso no habría sido necesario.

Sobrecarga de constructores de clases derivadas

Por supuesto, los constructores de las clases derivadas también pueden sobrecargarse, podemos crear distintos constructores para diferentes inicializaciones posibles, y también usar parámetros con valores por defecto.

Destructores de clases derivadas

Cuando se destruye un objeto de una clase derivada, primero se invoca al destructor de la clase derivada, si existen objetos miembro a continuación se invoca a sus destructores y finalmente al destructor de la clase o clases base. Si la clase base es a su vez una clase derivada, el proceso se repite recursivamente.

Al igual que pasaba con los constructores, si no hemos definido los destructores de las clases, se usan los destructores por defecto que crea el compilador.

Veamos un ejemplo:

#include <iostream>
using namespace std;

class ClaseA {
  public:
   ClaseA() : datoA(10) { 
      cout << "Constructor de A" << endl; 
   } 
   ~ClaseA() { cout << "Destructor de A" << endl; }
   int LeerA() const { return datoA; }
   
  protected:
   int datoA;
};

class ClaseB : public ClaseA {
  public:
   ClaseB() : datoB(20) { 
      cout << "Constructor de B" << endl; 
   }
   ~ClaseB() { cout << "Destructor de B" << endl; }
   int LeerB() const { return datoB; }

  protected:
   int datoB;
};

int main() {
   ClaseB objeto;
   
   cout << "a = " << objeto.LeerA() << ", b = " 
        << objeto.LeerB() << endl;
   
   return 0;
}

La salida es esta:

Constructor de A
Constructor de B
a = 10, b = 20
Destructor de B
Destructor de A

Se ve que primero se llama al destructor de la clase derivada B, y después al de la clase base A.

Comentarios de los usuarios (5)

aJ
2011-03-18 00:55:06

En el ejemplo de "Inicialización de objetos miembros de clases" del tema de la Herencia, me da error al copilarlo en Code Blocks, Dev C++ y Visual Studio C++ Express 2010.

Steven
2011-03-18 03:57:35

Hola aJ,

Ciertamente, el compilador nos marca un aviso, que es debido a una regla semántica que el ejemplo no tomó en cuenta. Intentaremos modificar o corregir el ejemplo, en cuanto podamos.

La regla semántica dícese así:

"Los datos miembro (no estáticos) son inicializados en el orden que fueron declarados en la definición de la clase (irrelevantemente del orden de los miembros de la lista inicializadora".

Es decir, tal y como definimos 'ClaseB', se inicializará el dato miembro, 'datoB', y luego 'cA', porque así aparecen sus declaraciones en la clase 'ClaseB'. Este orden no tiene nada que ver con el orden de su inicialización en el constructor. Al escribir esto:

ClaseB( int a, int b ) : cA(a), datoB(b)  {...}

la inicialización no compagina con el orden verdadero de la inicialización de estos dos datos miembro.

Podríamos invertir el orden de los miembros de la lista inicializadora, de esta manera:

ClaseB( int a, int b ) : datoB(b), cA(a)  {...}

La otra solución es invirtiendo el orden de las declaraciones de los datos miembro en 'ClaseB', sin cambiar los miembros de la lista inicializadora; esto es,

class ClaseB
{
  ClaseB( int a, int b ) : cA(a), datoB(b)  {...}
  ...
  ClaseA cA;
  int datoB;
}

En este caso, no tenemos muchos problemas, porque cada dato miembro es independiente entre sí, pero supongo que es una buena idea conocer el orden de inicialización para que no haya sorpresas en otros casos.

Espero haber aclarado la duda.

Steven

aJ
2011-03-18 04:17:03

Gracias Steven ya aclare la duda con tu respuesta.

Milton Parra
2014-03-14 20:54:37

El ejemplo de DESTRUCTORES en clases derivadas no me ejecuta el destructor usando el programa DEV-C++. Lo único nuevo que hice en el equipo fué instalar el software CODEBLOCKS. Será que desconfiguré algún parámetro? Agradezco su ayuda por favor.

Javier
2014-08-08 19:09:56

Hola. Me preguntaba si es válido que en una clase derivada, exista un miembro objeto de la clase base, es decir:

#include <iostream>
using namespace std;

class ClaseA {
  public:
   ClaseA() : datoA(10) { 
      cout << "Constructor de A" << endl; 
   }   
  protected:
   int datoA;
};

class ClaseB : public ClaseA {
  public:
   ClaseB() : datoB(20) { 
      cout << "Constructor de B" << endl; 
   }
  protected:
   ClaseA objeto;
};

Muchas gracias.