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' existeObserve 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.
Desafio
FácilVamos 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 comumEmployeecom:- Um
std::string namee umint 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 use 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 use 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 herde publicamente deDevelopereManager:- Um construtor que receba os quatro parâmetros e inicialize diretamente
Employee(a classe-base virtual), depoisDevelopereManager - O construtor deve imprimir:
TeamLead [<name>] ready to lead and code! - Um método
showRole()que chamegetInfo(),code()emanage(), nessa ordem - Um destrutor que imprima:
TeamLead [<name>] promoted out
Crie um objeto
TeamLeaddentro de um escopo de bloco, chameshowRole()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;
}
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 POO
Arquivos externosBuild e compilação em C++Arquivos de cabeçalho e arquivos-fonteNamespaces e escopoIntrodução à POO em C++Classes vs. objetosO ponteiro 'this'Métodos (funções membro)Atributos (membros de dados)Noções básicas de construtores e destruidoresRevisão - calculadora simples4Propriedades de Classe
Membros de Instância vs. EstáticosGetters e SettersFunções Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRevisão - Gerenciador de Contas Bancárias7Herança
Herança básicaNíveis de acesso da herançaOrdem de chamada de construtor e destrutorSobrescrita de métodosFunções virtuais e VTableHerança múltiplaHerança virtualRevisão - Hierarquia de funcionários10Visão geral da STL
Visão geral e filosofia da STLContêineres da STLIteradoresAlgoritmos da STLFunctors e expressões lambdaRecapitulação - Frequência de palavras13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonFactory e Abstract FactoryPadrão BuilderPadrão ObserverPadrão Strategy2Gerenciamento de Memória
Memória Stack vs. HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Ponteiros Inteligentes em C++RAII em C++Revisão - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de acesso em C++Especificadores de acesso em profundidadeOcultação de informaçõesStruct vs ClasseClasses aninhadas e internasRecapitulação - Sistema de registros de alunos8Polimorfismo
Polimorfismo em compilação vs. execuçãoSobrecarga de funçõesFunções virtuais revisadasFunções virtuais purasClasses abstratasDesign de interfaces em C++Conversão dinâmica e RTTIRecapitulação — Calculadora de formas11Conceitos Avançados de OOP
Composição vs. HerançaMixins via CRTPIdioma PimplApagamento de TiposClasses Enum e Tipagem ForteTratamento de Exceções em OOPHierarquias de Exceções Personalizadas14Padrões de Projeto — Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentoListas de Inicialização do ConstrutorConstrutores DelegantesAprofundamento em DestrutoresRegra dos Três / Cinco / ZeroRevisão - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de FluxoSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçõesTemplates de ClassesEspecialização de TemplatesTemplates VariádicosNoções Básicas de SFINAE e Traits de TiposRecapitulação - Contêiner GenéricoPratique por conta própria: Compilador de C++ online