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' existeObserve 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.
Desafio
FácilVamos 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 comumEmployeecom:- Um
std::string nameeint idprotegidos - 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
- Um
Developer.h: Defina uma classeDeveloperque usa herança pública virtual deEmployee:- 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
- Um membro protegido
Manager.h: Defina uma classeManagerque usa herança pública virtual deEmployee:- 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
- Um membro protegido
main.cpp: Leia quatro entradas (cada uma em uma linha separada):- Nome (string)
- ID do funcionário (inteiro)
- Linguagem de programação (string)
- Tamanho da equipe (inteiro)
Defina uma classe
TeamLeadque herda publicamente deDevelopereManager:- Um construtor que recebe todos os quatro parâmetros e deve inicializar diretamente
Employee(a base virtual), depoisDevelopereManager - O construtor deve imprimir:
TeamLead [<name>] ready to lead and code! - Um método
showRole()que chamagetInfo(),code()emanage()nessa ordem - Um destrutor que imprime:
TeamLead [<name>] promoted out
Crie um objeto
TeamLeaddentro de um escopo de bloco, chameshowRole()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' existeCom 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;
}
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
1Fundamentos de OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico