A herança virtual em C++ é um mecanismo projetado para resolver ambiguidades e duplicações que surgem em hierarquias de herança múltipla, especialmente no chamado problema do diamante>. Quando duas ou mais classes derivadas herdam de uma mesma classe base — e uma classe final herda dessas duas — a ausência de virtualização resulta em múltiplas instâncias da classe base no objeto final. Isso compromete a integridade semântica, consome memória desnecessariamente e dificulta o acesso consistente a membros compartilhados.
1. Sintaxe e contexto de aplicação
A virtualização não é declarada na definição da classe base, mas sim no especificador de herança de cada classe intermediária. A palavra-chave virtual precede o modo de acesso (public, protected ou private):
struct Base {
int valor = 42;
};
struct IntermediariaUm : virtual public Base {
IntermediariaUm() = default;
};
struct IntermediariaDois : virtual public Base {
IntermediariaDois() = default;
};
struct Final : public IntermediariaUm, public IntermediariaDois {
Final() = default;
};
2. Comportamento de inicialização
Quando a classe base virtual possui construtores parametrizados (sem construtor padrão), a inicialização direta deve ocorrer exclusivamente na classe mais derivada da hierarquia. Classes intermediárias não devem inicializá-la — caso contrário, o compilador gera erro ou comportamento indefinido. Exemplo:
struct Configuravel : virtual public Base {
Configuravel(int x) : Base{x} {} // ❌ Inválido: não é a classe mais derivada
};
struct Topo : public IntermediariaUm, public IntermediariaDois {
Topo(int v) : Base{v}, IntermediariaUm{}, IntermediariaDois{} {}
// ✅ Apenas 'Topo' chama explicitamente Base(v)
};
3. Ordem de construção e destruição
O compilador impõe uma ordem estrita:
- Construção: primeiro as classes bases virtuais (na ordem de sua primeira aparição na hierarquia), depois as não virtuais (na ordem de declaração na lista de herança), por fim a classe mais derivada.
- Destruição: ordem inversa — começa pela classe mais derivada, segue pelas não virtuais (em ordem inversa de declaração), e termina com as virtuais.
4. Impactos estruturais e de desempenho
Cada objeto que participa de herança virtual contém um ponteiro implícito (vbptr) apontando para uma tabela de deslocamentos (vbase table). Isso acarreat:
- Aumento fixo no tamanho do objeto (geralmente 8 bytes em sistemas de 64 bits);
- Sobrecarga indireta no acesso a membros da base virtual (necessidade de resolução em tempo de execução via tabela);
- Maior complexidade na geração de código pelo compilador, especialmente em otimizações de inlining.
5. Boas práticas de projeto
- Evite herança múltipla sempre que uma composição ou interface pura (classe abstrata com apenas métodos virtuais puros) for suficiente;
- Se usar herança virtual, declare-a consistente e uniformemente em todas as classes que herdam diretamente da base — caso contrário, a virtualização perde eficácia;
- Nunca confunda herança virtual com funções virtuais: são conceitos ortogonais — uma lida com estrutura de dados, outra com vinculação dinâmica de chamadas;
- Considere tornar o destrutor da classe base virtual se houver polimorfismo de destruição (ex.:
deletevia ponteiro base), mesmo que ela não participe de herança virtual.
6. Casos críticos a evitar
Não herde virtualmente de classes projetadas como exception types (como std::exception ou derivadas) sem revisão cuidadosa: muitas bibliotecas esperam layout trivial e não suportam vbptr. Da mesma forma, evite virtualizar classes usadas em contextos de baixo nível (ex.: drivers, firmware embarcado) onde o overhead de indireção é inaceitável.