Capítulo 11: Herança, Polimorfismo Dinâmico e a Mecânica da VTable

Especialização em Sistemas com C++ • C++20 & C++23 • RAII, Smart Pointers, Templates, Concorrência e Alta Performance


🗺️ Mapa Conceitual do Layout de Memória e Despacho Polimórfico (VTable)

flowchart TD
    subgraph OBJETO_RAM["Layout em Memória da Instância Derivada"]
        VPTR["__vptr (Ponteiro Oculto de 8 Bytes para VTable)"]
        CamposBase["Membros de Dados da Classe Base"]
        CamposDerivada["Membros de Dados da Classe Derivada"]
    end

    subgraph VTABLE_RODATA["Tabela de Métodos Virtuais (.rodata)"]
        VTablePtr1["&ClasseDerivada::executar()"]
        VTablePtr2["&ClasseDerivada::calcular()"]
        VTableDtor["&ClasseDerivada::~ClasseDerivada()"]
    end

    VPTR --> VTABLE_RODATA

    Chamada["ponteiro_base->executar()"] -->|1. Desreferencia __vptr| VTABLE_RODATA
    VTABLE_RODATA -->|2. Desvia Execução| Funcao["Executa Código Real da Classe Derivada"]

    style OBJETO_RAM fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style VTABLE_RODATA fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style Chamada fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style Funcao fill:#e8f5e9,stroke:#4caf50,stroke-width:2px

🏛️ 1. O Despacho Dinâmico Sob o Capô: A Mecânica da VTable

Quando uma classe declara pelo menos um método virtual (virtual), o compilador C++ transforma o modelo de despacho de funções. Em vez de uma chamada direta resolvida em tempo de compilação/linkagem (call endereco_fixo), o método é despachado indiretamente através de uma tabela de ponteiros de função chamada VTable (Virtual Method Table).

O Custo Físico do Polimorfismo:

  1. Ponteiro Virtual Oculto (__vptr): Toda instância de uma classe polimórfica ganha um campo oculto extra de 8 bytes (em sistemas de 64 bits) inserido no início de sua memória para apontar para a VTable correspondente ao seu tipo real.
  2. Dupla Indireção na Chamada: O processador deve primeiro carregar o endereço da VTable apontado por __vptr e, em seguida, indexar a tabela para desviar a execução para o código alvo.
  3. Barreira ao Inlining: Compiladores geralmente não conseguem fazer inline de chamadas polimórficas virtuais em tempo de compilação, a menos que consigam provar a devirtualização estática (devirtualization).

⚙️ 2. Regra Mandatória: O Destrutor Virtual

Se você possuir uma hierarquia polimórfica onde objetos derivados são manipulados através de ponteiros ou referências para a classe base (std::unique_ptr<Base> ptr = std::make_unique<Derivada>()), o destrutor da classe base DEVE ser virtual:

class DispositivoBase {
public:
    // Obrigatório! Garante que ~DispositivoSerial() seja chamado antes de ~DispositivoBase()
    virtual ~DispositivoBase() = default;

    virtual void inicializar() = 0; // Método virtual puro (interface)
};

[!CAUTION] O Perigo da Destruição Incompleta Se o destrutor base não for virtual, invocar delete em um ponteiro Base* que aponta para um objeto derivado resulta em Comportamento Indefinido (UB). O compilador chamará apenas o destrutor base, causando vazamento de todos os recursos alocados na classe derivada!


📦 3. Boas Práticas Modernas: override e final

Para prevenir erros sutis de assinatura em sobrecargas de métodos virtuais (como pequenas diferenças de parâmetros int vs long ou esquecimento do qualificador const), o C++ introduziu as palavras-chave contextuais:

  • override: Instrui o compilador a verificar estritamente se o método está de fato sobrescrevendo um método virtual puro ou existente na classe base. Se houver discrepância na assinatura, o build falha imediatamente.
  • final: Impede que uma classe seja herdada posteriormente, ou que um método virtual seja sobrescrito por classes filhas subsequentes. O uso de final permite ao compilador aplicar devirtualização agressiva, transformando chamadas virtuais em chamadas diretas ultra-rápidas.
class Modem5G final : public DispositivoBase {
public:
    void inicializar() override {
        // Implementação garantida
    }
};

📊 4. Matriz Comparativa: Polimorfismo Dinâmico vs Polimorfismo Estático

Dimensão Polimorfismo Dinâmico (VTable) Polimorfismo Estático (Templates / Concepts)
Mecanismo de Despacho Tabela virtual em tempo de execução Resolução de tipos em tempo de compilação
Overhead de Memória +8 bytes por instância (__vptr) Zero bytes adicionais
Overhead de CPU Custo de salto indireto e cache miss Zero (possibilita 100% de inlining)
Coleções Heterogêneas Simples (std::vector<std::unique_ptr<Base>>) Requer std::variant ou tuplas