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;
}