Menu
Coddy logo textTech

Herança Virtual

Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 54 de 104.

O problema do diamante ocorre quando uma classe herda de duas classes que compartilham uma classe base comum. Sem um tratamento especial, a classe derivada acaba com duas cópias da base comum, causando ambiguidade e desperdício de memória.

class Animal {
public:
    int age;
};

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

class Bat : public Mammal, public Bird {};

Bat b;
b.age = 5;  // Erro: ambíguo - qual 'age'?

A classe Bat contém dois subobjetos Animal separados: um através de Mammal e um através de Bird. Isso cria o diagrama de herança em forma de diamante que dá nome ao problema.

Herança virtual resolve isso garantindo que exista apenas uma cópia da base comum. Adicione a palavra-chave virtual ao herdar da base compartilhada:

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! Apenas um 'age' existe

Observe que Bat deve inicializar diretamente Animal em seu construtor. Com herança virtual, a classe mais derivada é responsável por construir a base virtual, independentemente das classes intermediárias.

challenge icon

Desafio

Fácil

Vamos construir um sistema de gerenciamento de trabalhadores que demonstra como a herança virtual resolve o problema do diamante. Você criará uma hierarquia onde um TeamLead herda tanto de Developer quanto de Manager, que compartilham uma classe base comum Employee.

Você organizará seu código em quatro arquivos:

  • Employee.h: Defina a classe base comum Employee com:
    • Um std::string name e int id protegidos
    • Um construtor que recebe ambos os valores e imprime: Employee [<name>] hired with ID <id>
    • Um método público getInfo() que imprime: Employee: <name> (ID: <id>)
    • Um destrutor virtual que imprime: Employee [<name>] record closed
  • Developer.h: Defina uma classe Developer que usa herança pública virtual de Employee:
    • Um membro protegido std::string language
    • Um construtor que recebe nome, id e linguagem — passa nome e id para Employee, armazena a linguagem e imprime: Developer [<name>] specializes in <language>
    • Um método público code() que imprime: <name> is coding in <language>
    • Um destrutor que imprime: Developer [<name>] signed off
  • Manager.h: Defina uma classe Manager que usa herança pública virtual de Employee:
    • Um membro protegido int teamSize
    • Um construtor que recebe nome, id e tamanho da equipe — passa nome e id para Employee, armazena o tamanho da equipe e imprime: Manager [<name>] leads a team of <teamSize>
    • Um método público manage() que imprime: <name> is managing <teamSize> people
    • Um destrutor que imprime: Manager [<name>] stepped down
  • main.cpp: Leia quatro entradas (cada uma em uma linha separada):
    1. Nome (string)
    2. ID do funcionário (inteiro)
    3. Linguagem de programação (string)
    4. Tamanho da equipe (inteiro)

    Defina uma classe TeamLead que herda publicamente de Developer e Manager:

    • Um construtor que recebe todos os quatro parâmetros e deve inicializar diretamente Employee (a base virtual), depois Developer e Manager
    • O construtor deve imprimir: TeamLead [<name>] ready to lead and code!
    • Um método showRole() que chama getInfo(), code() e manage() nessa ordem
    • Um destrutor que imprime: TeamLead [<name>] promoted out

    Crie um objeto TeamLead dentro de um escopo de bloco, chame showRole() e deixe-o sair de escopo. Após o bloco, imprima: Organization restructured!

Por exemplo, com as entradas Alice, 101, C++ e 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!

Observe como há apenas uma chamada de construtor de Employee e uma chamada de destrutor de Employee — a herança virtual garante que apenas uma cópia da base compartilhada exista. O TeamLead deve inicializar diretamente Employee porque, com herança virtual, a classe mais derivada é responsável por construir a base virtual.

Folha de consulta

O problema do diamante ocorre quando uma classe herda de duas classes que compartilham uma classe base comum, resultando em duas cópias da classe base e causando ambiguidade.

class Animal {
public:
    int age;
};

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

class Bat : public Mammal, public Bird {};

Bat b;
b.age = 5;  // Erro: ambíguo - qual 'age'?

A herança virtual resolve o problema do diamante garantindo que apenas uma cópia da classe base comum exista. Use a palavra-chave virtual ao herdar:

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! Apenas um 'age' existe

Com a herança virtual, a classe mais derivada deve inicializar diretamente a classe base virtual em seu construtor, independentemente das classes intermediárias.

Experimente você mesmo

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

using namespace std;

// TODO: Defina a classe TeamLead que herda publicamente de Developer e Manager
// Lembre-se: Com herança virtual, TeamLead deve inicializar diretamente Employee (a base virtual)
class TeamLead : public Developer, public Manager {
public:
    // TODO: Implemente o construtor que recebe name, id, language e teamSize
    // Deve inicializar: Employee primeiro (base virtual), depois Developer, depois Manager
    // Deve imprimir: 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: Imprima a mensagem do construtor
    }

    // TODO: Implemente o método showRole()
    // Deve chamar getInfo(), code() e manage() nessa ordem
    void showRole() {
        // TODO: Chame os três métodos
    }

    // TODO: Implemente o destrutor
    // Deve imprimir: TeamLead [<name>] promoted out
    ~TeamLead() {
        // TODO: Imprima a mensagem do destrutor
    }
};

int main() {
    // Leia as entradas
    string name;
    int id;
    string language;
    int teamSize;

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

    // TODO: Crie um objeto TeamLead dentro de um escopo de bloco
    // Chame showRole(), então deixe-o sair de escopo
    {
        // TODO: Crie TeamLead e chame showRole()
    }

    // Imprima a mensagem final após o bloco
    cout << "Organization restructured!" << endl;

    return 0;
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos