Polimorfismo em C++: Diferenças entre Funções Virtuais e Funções Virtuais Puras

Em C++, o polimorfismo e a implementação de interfaces dependem fundamentalmente de dois conceitos: funções virtuais e funções virtuais puras. Embora ambos utilizem o mecanismo de ligação dinâmica, suas aplicações, regras de definição e impactos no design de classes divergem significativamente.

Funções Virtuais: Implementação com Comportamento Padrão

Uma função virtual é declarada na classe base utilizando a palavra-chave virtual. Seu principal objetivo é permitir que as classes derivadas sobrescrevam (override) seu comportamento. Ao invocar essa função através de um ponteiro ou referência da classe base, o tempo de execução do C++ utiliza ligação tardia (late binding) para determinar qual implementação específica deve ser executada. A grande vantagem é que a classe base pode fornecer uma implementação padrão, não obrigando as subclasses a sobrescrevê-la.

#include <iostream>
#include <memory>

class Veiculo {
public:
    virtual void acelerar() const {
        std::cout << "Acelerando veiculo generico." << std::endl;
    }
    
    virtual ~Veiculo() = default;
};

class Carro : public Veiculo {
public:
    void acelerar() const override {
        std::cout << "Carro acelerando com motor a combustao." << std::endl;
    }
};

class Moto : public Veiculo {
public:
    void acelerar() const override {
        std::cout << "Moto acelerando com alta rotacao." << std::endl;
    }
};

int main() {
    auto veiculo1 = std::make_unique<Carro>();
    auto veiculo2 = std::make_unique<Moto>();

    Veiculo* ptr1 = veiculo1.get();
    Veiculo* ptr2 = veiculo2.get();

    ptr1->acelerar();
    ptr2->acelerar();

    return 0;
}

É uma prática essencial declarar um destruidor virtual na classe base. Se a classe base for usada polimorficamente (ou seja, deletando objetos derivados através de ponteiros da classe base), a ausência de um destruidor virtual impedirá a chamada do destruidor da classe derivada, resultando em vazamento de recursos.

Funções Virtuais Puras: Definindo Contratos e Classes Abtsratas

Uma função virtual pura é uma variante especial que não possui implementação na classe base (sinalizada por = 0 na declaração). Ela é utilizada para definir contratos estritos, forçando as classes derivadas a fornecerem sua própria implementação. A presença de pelo menos uma função virtual pura torna a classe uma classe abstrata, o que significa que ela não pode ser instanciada diretamente.

#include <iostream>

class RepositorioDados {
public:
    virtual void buscarRegistro(int id) const = 0;
    virtual ~RepositorioDados() = default;
};

class BancoDadosSQL : public RepositorioDados {
public:
    void buscarRegistro(int id) const override {
        std::cout << "Executando query SQL para ID: " << id << std::endl;
    }
};

class ArmazenamentoNuvem : public RepositorioDados {
public:
    void buscarRegistro(int id) const override {
        std::cout << "Fazendo requisicao HTTP para ID: " << id << std::endl;
    }
};

int main() {
    // RepositorioDados repo; // Erro de compilacao: classe abstrata

    RepositorioDados* repositorio = nullptr;

    BancoDadosSQL sql;
    repositorio = &sql;
    repositorio->buscarRegistro(101);

    ArmazenamentoNuvem nuvem;
    repositorio = &nuvem;
    repositorio->buscarRegistro(102);

    return 0;
}

Caracteristicas Fundamentais

  • Ausencia de corpo na base: A classe base apenas declara a assinatura, exigindo que a lógica seja construída nas subclasses.
  • Instanciacao bloqueada: Classes abstratas servem apenas como interfaces ou bases parciais, não permitindo a criação de objetos diretos.
  • Obrigatoriedade de sobrescrita: Para que uma classe derivada seja concreta (instanciável), ela deve implementar todas as funções virtuais puras herdadas.
  • Membros concretos permitidos: Ao contrário do que se pensa, classes abstratas podem conter métodos normais, atributos e até mesmo destruidores virtuais com implementação.
  • Destruidores virtuais puros: Se um destruidor for declarado como puro (virtual ~Classe() = 0;), a classe se torna abstrata, mas o destruidor ainda deve ter uma implementação fora da declaração, pois a cadeia de destruição o invocará.

Aplicacao Pratica: Colecoes Polimorficas

O uso de funções virtuais puras é extremamente comum ao gerenciar coleções de objetos heterogêneos que compartilham uma interface comum, permitindo a iteração e execução de comportamentos específicos de cada tipo.

#include <iostream>
#include <vector>
#include <string>

class ServicoNotificacao {
public:
    virtual void enviar(std::string const& mensagem) const = 0;
    virtual ~ServicoNotificacao() = default;
};

class Email : public ServicoNotificacao {
public:
    void enviar(std::string const& mensagem) const override {
        std::cout << "Enviando email: " << mensagem << std::endl;
    }
};

class SMS : public ServicoNotificacao {
public:
    void enviar(std::string const& mensagem) const override {
        std::cout << "Enviando SMS: " << mensagem << std::endl;
    }
};

void processarNotificacoes(std::vector<ServicoNotificacao const*> const& servicos) {
    for (auto const& servico : servicos) {
        servico->enviar("Alerta de sistema");
    }
}

int main() {
    Email emailServico;
    SMS smsServico;

    std::vector<ServicoNotificacao const*> canais = {&emailServico, &smsServico};
    processarNotificacoes(canais);

    return 0;
}

Tags: C++ Polimorfismo funcoes-virtuais classes-abstratas late-binding

Publicado em 9-18 11:57