Menu
Coddy logo textTech

Herencia virtual

Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy. Lección 54 de 104.

El problema del diamante ocurre cuando una clase hereda de dos clases que comparten una clase base común. Sin un tratamiento especial, la clase derivada termina con dos copias de la base común, lo que provoca ambigüedad y desperdicio de memoria.

class Animal {
public:
    int age;
};

class Mammal : public Animal {};
class Bird : public Animal {};

class Bat : public Mammal, public Bird {};

Bat b;
b.age = 5;  // Error: ambiguo - ¿cuál 'age'?

La clase Bat contiene dos subobjetos Animal separados: uno a través de Mammal y otro a través de Bird. Esto crea el diagrama de herencia con forma de diamante que da nombre al problema.

La herencia virtual resuelve esto al garantizar que solo exista una copia de la base común. Añade la palabra clave virtual al heredar de la base compartida:

class Animal {
public:
    int age;
    Animal(int a = 0) : age(a) {}
};

class Mammal : virtual public Animal {
public:
    Mammal(int a = 0) : Animal(a) {}
};

class Bird : virtual public Animal {
public:
    Bird(int a = 0) : Animal(a) {}
};

class Bat : public Mammal, public Bird {
public:
    Bat(int a) : Animal(a), Mammal(a), Bird(a) {}
};

Bat b(5);
b.age = 10;  // ¡Funciona! Solo existe un 'age'

Observa que Bat debe inicializar directamente Animal en su constructor. Con la herencia virtual, la clase más derivada es responsable de construir la base virtual, independientemente de las clases intermedias.

challenge icon

Desafío

Fácil

Construyamos un sistema de gestión de trabajadores que demuestre cómo la herencia virtual resuelve el problema del diamante. Crearás una jerarquía en la que un TeamLead hereda tanto de Developer como de Manager, que comparten una clase base común Employee.

Organizarás tu código en cuatro archivos:

  • Employee.h: Define la clase base común Employee con:
    • Un std::string name y un int id protegidos
    • Un constructor que recibe ambos valores e imprime: Employee [<name>] hired with ID <id>
    • Un método público getInfo() que imprime: Employee: <name> (ID: <id>)
    • Un destructor virtual que imprime: Employee [<name>] record closed
  • Developer.h: Define una clase Developer que usa herencia pública virtual de Employee:
    • Un miembro protegido std::string language
    • Un constructor que recibe el nombre, el identificador y el lenguaje: pasa el nombre y el identificador a Employee, almacena el lenguaje e imprime: Developer [<name>] specializes in <language>
    • Un método público code() que imprime: <name> is coding in <language>
    • Un destructor que imprime: Developer [<name>] signed off
  • Manager.h: Define una clase Manager que usa herencia pública virtual de Employee:
    • Un miembro protegido int teamSize
    • Un constructor que recibe el nombre, el identificador y el tamaño del equipo: pasa el nombre y el identificador a Employee, almacena el tamaño del equipo e imprime: Manager [<name>] leads a team of <teamSize>
    • Un método público manage() que imprime: <name> is managing <teamSize> people
    • Un destructor que imprime: Manager [<name>] stepped down
  • main.cpp: Lee cuatro entradas (cada una en una línea separada):
    1. Nombre (cadena)
    2. Identificador del empleado (entero)
    3. Lenguaje de programación (cadena)
    4. Tamaño del equipo (entero)

    Define una clase TeamLead que herede públicamente tanto de Developer como de Manager:

    • Un constructor que reciba los cuatro parámetros y deba inicializar directamente Employee (la base virtual), y después Developer y Manager
    • El constructor debe imprimir: TeamLead [<name>] ready to lead and code!
    • Un método showRole() que llame a getInfo(), code() y manage() en ese orden
    • Un destructor que imprima: TeamLead [<name>] promoted out

    Crea un objeto TeamLead dentro de un ámbito de bloque, llama a showRole() y después deja que salga de ámbito. Después del bloque, imprime: Organization restructured!

Por ejemplo, con las entradas Alice, 101, C++ y 5:

Employee [Alice] hired with ID 101
Developer [Alice] specializes in C++
Manager [Alice] leads a team of 5
TeamLead [Alice] ready to lead and code!
Employee: Alice (ID: 101)
Alice is coding in C++
Alice is managing 5 people
TeamLead [Alice] promoted out
Manager [Alice] stepped down
Developer [Alice] signed off
Employee [Alice] record closed
Organization restructured!

Observa que solo hay una llamada al constructor de Employee y una llamada al destructor de Employee: la herencia virtual garantiza que solo exista una copia de la base compartida. TeamLead debe inicializar directamente Employee porque, con la herencia virtual, la clase más derivada es responsable de construir la base virtual.

Pruébalo tú mismo

#include <iostream>
#include <string>
#include "Developer.h"
#include "Manager.h"

using namespace std;

// TODO: Define la clase TeamLead que hereda públicamente de Developer y Manager
// Recuerda: Con herencia virtual, TeamLead debe inicializar directamente Employee (la base virtual)
class TeamLead : public Developer, public Manager {
public:
    // TODO: Implementa el constructor que toma name, id, language y teamSize
    // Debe inicializar: Employee primero (base virtual), luego Developer, luego Manager
    // Should print: TeamLead [<name>] ready to lead and code!
    TeamLead(const std::string& name, int id, const std::string& language, int teamSize)
        : Employee(name, id),
          Developer(name, id, language),
          Manager(name, id, teamSize) {
        // TODO: Imprime el mensaje del constructor
    }

    // TODO: Implementa el método showRole()
    // Debe llamar a getInfo(), code() y manage() en ese orden
    void showRole() {
        // TODO: Llama a los tres métodos
    }

    // TODO: Implementa el destructor
    // Should print: TeamLead [<name>] promoted out
    ~TeamLead() {
        // TODO: Imprime el mensaje del destructor
    }
};

int main() {
    // Leer las entradas
    string name;
    int id;
    string language;
    int teamSize;

    getline(cin, name);
    cin >> id;
    cin.ignore();
    getline(cin, language);
    cin >> teamSize;

    // TODO: Crea un objeto TeamLead dentro de un ámbito de bloque
    // Llama a showRole(), luego déjalo salir del ámbito
    {
        // TODO: Crea TeamLead y llama a showRole()
    }

    // Imprime el mensaje final después del bloque
    cout << "Organization restructured!" << endl;

    return 0;
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación Orientada a Objetos

Practica por tu cuenta: Compilador de C++ online