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 tendo duas cópias da classe 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 por meio de Mammal e outro por meio 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 a 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 criar um sistema de gerenciamento de funcionários que demonstra como a herança virtual resolve o problema do diamante. Você criará uma hierarquia na qual um TeamLead herda de Developer e 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 um 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 use 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 use 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 herde publicamente de Developer e Manager:

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

    Crie um objeto TeamLead dentro de um escopo de bloco, chame showRole() e depois permita que ele saia do 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 que há apenas uma chamada ao construtor de Employee e uma chamada ao destrutor de Employee: a herança virtual garante que exista apenas uma cópia da classe-base compartilhada. O TeamLead deve inicializar diretamente Employee porque, com a herança virtual, a classe mais derivada é responsável por construir a classe-base virtual.

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
    // 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: 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
    // Should print: TeamLead [<name>] promoted out
    ~TeamLead() {
        // TODO: Imprima a mensagem do destrutor
    }
};

int main() {
    // Lê 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(), depois deixe-o sair do 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

Pratique por conta própria: Compilador de C++ online