Introducción: por qué los constructores de los padres son un campo minado para el principiante
Cualquiera que haya comenzado a estudiar herencia en C++, tarde o temprano se ha encontrado con esta situación: creas una clase derivada, el compilador se queja, el programa no compila y no entiendes por qué. La mayoría de las veces, la raíz del mal está en una llamada incorrecta al constructor de la clase base. Esta pregunta en Stack Overflow ha acumulado miles de visitas, y no es para menos: incluso los desarrolladores experimentados a veces olvidan las reglas básicas.
En este artículo analizaremos las reglas para llamar al constructor de la clase base en C++, repasaremos los 5 errores más comunes y mostraremos cómo evitarlos. Si quieres escribir código robusto sin sorpresas, lee hasta el final.
Breve introducción: cómo funcionan los constructores en la herencia
En C++, los constructores no se heredan. Cuando creas un objeto de una clase derivada, primero se llama al constructor de la clase base (implícita o explícitamente) y luego al constructor de la clase derivada. Si no especificas qué constructor del padre llamar, el compilador intentará llamar al constructor por defecto de la clase base. Si no existe, obtendrás un error de compilación.
La llamada explícita se realiza a través de la lista de inicialización:
class Base {public: Base(int x) { /* ... */ }};
class Derived : public Base {public: // Correcto: llamada explícita al constructor Base(int) Derived() : Base(42) { /* ... */ }};¿Parece sencillo? Pero es aquí donde los principiantes cometen el 90% de los errores. Vamos a analizarlos en orden.
Top 5 errores al llamar al constructor de la clase base
Error n.º 1: Olvidar llamar al constructor del padre con parámetros
El problema más común. Si la clase base no tiene un constructor por defecto y no lo llamas explícitamente, el compilador dará un error.
class Base {public: Base(int x) { /* inicialización */ }};
class Derived : public Base {public: // ERROR: el compilador intenta llamar a Base(), que no existe Derived() { }};Cómo solucionarlo:
class Derived : public Base {public: // CORRECTO: llamamos explícitamente a Base(int) Derived() : Base(0) { } // O pasamos el parámetro desde fuera: Derived(int x) : Base(x) { }};Error n.º 2: Orden incorrecto de inicialización
Muchos piensan que el orden de las llamadas a los constructores en la lista de inicialización determina el orden de ejecución. No es así. La clase base siempre se inicializa primero, independientemente de dónde escribas su llamada.
class Derived : public Base {private: int value;public: // ¿Parece que value se inicializa primero? ¡No! Derived() : value(10), Base(5) { } // En realidad: primero Base(5), luego value(10)};Esto puede provocar errores lógicos si la inicialización de value depende del estado de la clase base.
Error n.º 3: Llamar al constructor del padre en el cuerpo del constructor
Algunos principiantes intentan llamar al constructor de la clase base dentro de las llaves, como si fuera un método normal. Esto no funciona: el constructor solo se puede llamar en la lista de inicialización.
class Derived : public Base {public: Derived() { // ERROR: así no se llama al constructor Base(10); // Simplemente crea un objeto temporal y lo elimina }};Error n.º 4: Confusión con los constructores de copia
Al implementar el constructor de copia en una clase derivada, debes llamar explícitamente al constructor de copia de la clase base. De lo contrario, se llamará al constructor por defecto y los datos de la clase base no se copiarán.
class Derived : public Base {public: // ERROR: Base se copiará incorrectamente Derived(const Derived& other) { /* ... */ }
// CORRECTO: Derived(const Derived&