Menu

Herencia en C++: clases base y derivadas explicadas

Aprende cómo la herencia en C++ permite que una clase derivada reutilice y amplíe una clase base: la sintaxis, herencia pública frente a privada, el orden de constructores y destructores, y trampas como el recorte de objetos (slicing).

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Reutilizar una clase ampliándola

Has construido clases con constructores y las has limpiado con destructores. La herencia es el siguiente paso: en lugar de copiar los miembros de una clase en otra, declaras que una clase nueva es una versión especializada de una existente y dejas que herede todo automáticamente.

La clase existente es la clase base (o padre); la nueva es la clase derivada (o hija). Una clase derivada empieza con todos los datos y el comportamiento de la base, y luego añade o cambia lo que la hace diferente. Esta es la herramienta principal de C++ para la relación "es-un": un Dog es un Animal, una SavingsAccount es una BankAccount.

Sintaxis básica: class Derived : public Base

Heredas escribiendo, tras el nombre de la clase derivada, dos puntos, un especificador de acceso y el nombre de la clase base. La forma más común es la herencia public.

Dog nunca declara name ni eat(), y aun así ambos funcionan sobre un objeto Dog porque se heredaron de Animal. La clase derivada es libre de añadir miembros como bark() de los que la base no sabe nada.

protected: miembros solo para las hijas

Un miembro private de la base no es accesible dentro de la clase derivada: la herencia no rompe la encapsulación. Cuando quieres que las interioridades de la base estén ocultas del mundo exterior pero disponibles para las subclases, usa el especificador de acceso protected.

Piensa en los tres niveles como anillos concéntricos: private es "solo esta clase", protected es "esta clase y sus descendientes" y public es "todo el mundo". Consulta especificadores de acceso para el panorama completo.

Orden de constructores y destructores

Un objeto derivado contiene un subobjeto base, y esa parte base debe estar viva antes de que se construya la parte derivada. Por eso la construcción se ejecuta de la base primero y la destrucción del derivado primero (el orden exactamente inverso). Si la base necesita argumentos de constructor, se los pasas a través de la lista de inicialización de miembros.

La salida hace concreto el orden:

Animal ctor: Rex   // la base se construye primero
Dog ctor           // luego la parte derivada
Dog dtor           // se destruye en orden inverso...
Animal dtor: Rex   // ...la base la última

Si olvidas el : Animal(n) y la base no tiene constructor por defecto, el código no compilará: C++ no tiene forma de saber cómo construir la parte base. Una clase base de la que pretendes heredar debería declarar casi siempre un destructor (y, como muestra la página siguiente, a menudo uno virtual).

Sobrescritura: redefinir un método base

Una clase derivada puede reemplazar un método heredado declarando uno con la misma firma. Aún puedes alcanzar el original a través de Base::method().

Esto es simple ocultación de nombres, no polimorfismo: qué describe() se ejecuta se decide en tiempo de compilación según el tipo estático de la variable. Esa es una limitación crucial: si llamas a través de un Shape& o un Shape* que en realidad apunta a un Circle, seguirás obteniendo Shape::describe(). Arreglar eso requiere virtual, que es el tema de la página siguiente.

Cuidado con el recorte de objetos (slicing)

Como una referencia o un puntero a la base pueden referirse a un objeto derivado, es tentador copiar un objeto derivado en una variable de tipo base. No lo hagas: la parte derivada se recorta.

a es un Animal genuino, no un Dog disfrazado con una etiqueta de base, así que breed simplemente no existe en él. Para trabajar con objetos derivados de forma polimórfica debes usar una referencia a la base (Animal&) o un puntero (Animal*), nunca un valor de la base. El slicing es silencioso: compila limpiamente y simplemente descarta datos sin avisar, lo que lo convierte en uno de los errores de herencia más fáciles de dejar pasar a producción.

Errores comunes que evitar

  • Esperar que los miembros private de la base sean accesibles en la hija. No lo son. Usa protected para los datos que la clase derivada necesita legítimamente, y mantén el estado verdaderamente interno como private.
  • Olvidar reenviar los argumentos del constructor base. Si la base no tiene constructor por defecto, debes llamarlo explícitamente en la lista de inicialización del constructor derivado (: Base(args)).
  • Recortar un objeto derivado en un valor base. Copiar un Dog en un Animal descarta todo lo específico de Dog. Pasa y almacena referencias o punteros a la base en su lugar.
  • Abusar de la herencia para reutilizar código. La herencia modela "es-un". Si la relación es en realidad "tiene-un" (un Car tiene un Engine), prefiere la composición - un objeto miembro - antes que heredar.

Siguiente: Funciones virtuales

La sobrescritura que acabas de ver se resolvía en tiempo de compilación, así que llamar a través de un puntero base ignoraba la versión derivada. La página siguiente, funciones virtuales, presenta la palabra clave virtual y override: el mecanismo que hace que el tipo en tiempo de ejecución decida qué método se ejecuta, desbloqueando el verdadero polimorfismo y explicando por qué las clases base necesitan destructores virtuales.

Preguntas frecuentes

¿Qué es la herencia en C++?

La herencia te permite definir una clase nueva (la clase derivada) a partir de una existente (la clase base). La clase derivada obtiene automáticamente los miembros de datos y las funciones miembro de la base, y puede añadir nuevos o reemplazar comportamientos existentes. Modela una relación "es-un" - un Dog es un Animal - y es la principal forma en que C++ reutiliza y amplía código a lo largo de jerarquías de clases.

¿Cuál es la diferencia entre herencia pública y privada en C++?

Con la herencia public (class Dog : public Animal), la interfaz pública de la base sigue siendo pública en la clase derivada, así que un Dog es-un Animal y puede usarse allí donde se espera un Animal. Con la herencia private, los miembros heredados pasan a ser privados: la clase derivada reutiliza la implementación de la base pero no es sustituible por ella. La herencia pública es con diferencia el caso habitual; recurre a la privada solo para reutilización del tipo "implementado-en-términos-de".

¿En qué orden se ejecutan los constructores y destructores con herencia en C++?

Los constructores se ejecutan de la base primero: la clase base se construye por completo antes de que se ejecute el cuerpo del constructor derivado. Los destructores se ejecutan en el orden exactamente inverso: primero el derivado y luego la base. Esto garantiza que, mientras un objeto derivado se está construyendo o destruyendo, cada parte de la que depende ya existe (o todavía existe).

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR