Polimorfismo em C++: Funcionamento, Tabelas Virtuais e Implementação

Conceito de Polimorfismo

O polimorfismo representa a capacidade de um objeto assumir múltiplas formas. No contexto da programação orientada a objetos em C++, ele permite que diferentes classes derivadas respondam de maneiras distintas a uma mesma chamada de função, dependendo do tipo do objeto que a invoca em tempo de execução.

Um exemplo clássico é o sistema de processamanto de pagamentos: uma classe base define um método de pagamento, mas as subclasses (Cartão de Crédito, Boleto, PIX) implementam regras de taxas e processamento específicas para cada modalidade.

Requisitos para o Polimorfismo Dinâmico

Para que o polimorfismo ocorra em C++, dois critérios técnicos devem ser atendidos:

  1. A chamada da função deve ser feita através de um ponteiro ou uma referência da classe base.
  2. A função chamada deve ser declarada como virtual na classe base e sobrescrita (override) na classe derivada.

Funções Virtuais e Sobrescrita

O uso da palavra-chave virtual sinaliza ao compilador que a resolução da função deve ser feita de forma dinâmica (Late Binding). Veja o exemplo abaixo:


#include <iostream>

class Conta {
public:
    virtual void calcularJuros() {
        std::cout << "Juros padrão: 1%" << std::endl;
    }
};

class ContaPoupanca : public Conta {
public:
    // Sobrescrita da função virtual
    virtual void calcularJuros() override {
        std::cout << "Juros Poupança: 0.5%" << std::endl;
    }
};

void ProcessarJuros(Conta& c) {
    c.calcularJuros();
}

int main() {
    Conta c1;
    ContaPoupanca cp1;
    
    ProcessarJuros(c1);  // Saída: Juros padrão: 1%
    ProcessarJuros(cp1); // Saída: Juros Poupança: 0.5%
    return 0;
}

A sobrescrita (override) exige que a assinatura da função (nome, parâmetros e retorno) seja idêntica à da classe base. Caso contrário, ocorrerá uma "ocultação" (hiding) em vez de polimorfismo.

Exceções na Sobrescrita: Destrutores e Covariância

Destrutores Virtuais

É uma boa prática definir o destrutor da classe base como virtual. Se um objeto de uma classe derivada for deletado através de um ponteiro da classe base, e o destrutor não for virtual, apenas o destrutor da base será chamado, causando vazamento de memória (memory leak).


class Base {
public:
    virtual ~Base() { std::cout << "Destrutor Base" << std::endl; }
};

class Derivada : public Base {
public:
    ~Derivada() { std::cout << "Destrutor Derivada" << std::endl; }
};

Covariância

A covariância permite que a função sobrescrita na classe derivada retorne um ponteiro ou referência para um objeto da própria classe derivada, enquanto a função base retorna um ponteiro ou referência para a classe base.

Palavras-chave: final e override

  • override: Garante que a função está realmente sobrescrevendo um método virtual da base. Se a assinatura não bater, o compilador gera um erro.
  • final: Quando aplicado a uma função, impede que ela seja sobrescrita por classes posteriores. Quando aplicado a uma classe, impede que ela seja herdada.

Classes Abstratas e Funções Virtuais Puras

Uma classe torna-se abstrata quando possui pelo menos uma função virtual pura, declarada com = 0. Essas classes não podem ser instanciadas e servem como interfaces.


class Forma {
public:
    virtual void desenhar() = 0; // Função virtual pura
};

class Circulo : public Forma {
public:
    void desenhar() override {
        std::cout << "Desenhando um círculo" << std::endl;
    }
};

Princípios de Implementação: O vptr e a vtable

Internamente, o polimorfismo é gerenciado por dois mecanismos:

  1. vtable (Virtual Functon Table): Um array estático criado pelo compilador para cada classe que contém funções virtuais. Ele armazena os endereços dessas funções.
  2. vptr (Virtual Table Pointer): Um ponteiro oculto adicionado a cada instância da classe. Ele aponta para a vtable correspondente ao tipo real do objeto.

Ao realizar uma chamada polimórfica, o programa segue o vptr do objeto, localiza a vtable e executa a função no endereço ali armazenado. Esse processo ocorre em tempo de execução, garantindo a flexibilidade do código.

Polimorfismo em Herança Múltipla

Na herança múltipla, uma classe derivada pode conter múltiplos ponteiros vptr, um para cada classe base que possui funções virtuais. Isso exige que o compilador realize um ajuste no ponteiro this (this-pointer adjustment) para garantir que o método da classe correta seja acessado, especialmente quando há sobreposição de assinaturas de métodos nas bases.


class Motor {
public:
    virtual void ligar() { std::cout << "Motor ligado" << std::endl; }
};

class Radio {
public:
    virtual void sintonizar() { std::cout << "Rádio sintonizado" << std::endl; }
};

class Carro : public Motor, public Radio {
public:
    void ligar() override { std::cout << "Carro pronto para rodar" << std::endl; }
};

Neste cenário, um objeto da classe Carro terá estruturas internas para referenciar as tabelas virtuais tanto de Motor quanto de Radio.

Vinculação Estática vs. Dinâmica

  • Vinculação Estática (Static Binding): Resolvida durante a compilação. Exemplos incluem sobrecarga de funções e templates. É mais eficiente em termos de performance.
  • Vinculação Dinâmica (Dynamic Binding): Resolvida durante a execução através da vtable. Proporciona maior flexibilidade e extensibilidade ao sistema.

Tags: C++ OOP vtable VirtualFunctions Polimorfismo

Publicado em 7-21 00:20