C++: Polimorfismo
Última atualização: 2026-08-26
Na Aula 31, aprendemos sobre herança — permitindo que classes derivadas reutilizem código da classe base.
Mas em cenários do mundo real, você precisa usar um ponteiro da classe base para tratar diferentes objetos de classes derivadas de forma uniforme.
Isso é polimorfismo — "uma interface, múltiplas implementações."
1. O que é Polimorfismo?
(1) 1.1 Polimorfismo no Dia a Dia
| Exemplo do Dia a Dia | Equivalente no Programa |
|---|---|
| Você manda um cachorro "falar", ele late | Chamar speak() em um ponteiro da classe base, mas executando a versão da classe derivada |
| Você manda um gato "falar", ele mia | A mesma chamada de função produz comportamento diferente para objetos diferentes |
A essência do polimorfismo: Usar um ponteiro da classe base para apontar para um objeto da classe derivada, de modo que chamar uma função virtual vincula dinamicamente à implementação da classe derivada.
(2) 1.2 Por que Precisamos de Polimorfismo?
Sem polimorfismo (tedioso):
▶ Exemplo 2: Demonstração de Programação Orientada a Objetos (Dificuldade ⭐)
#include <iostream>
class Dog {
public:
void speak() { std::cout << "Au!" << std::endl; }
};
class Cat {
public:
void speak() { std::cout << "Miau!" << std::endl; }
};
int main() {
Dog* d = new Dog();
Cat* c = new Cat();
d->speak();
c->speak();
// ❌ Precisa tratar tipos diferentes separadamente
return 0;
}
Saída:
Au!
Miau!
Com polimorfismo (conciso):
#include <iostream>
class Animal {
public:
virtual void speak() { std::cout << "Animal fala" << std::endl; }
};
class Dog : public Animal {
public:
void speak() override { std::cout << "Au!" << std::endl; }
};
class Cat : public Animal {
public:
void speak() override { std::cout << "Miau!" << std::endl; }
};
int main() {
Animal* animals = {new Dog(), new Cat()};
for (int i = 0; i < 2; i++) {
animals[i]->speak(); // ✅ Polimorfismo: chama automaticamente a versão correta
}
return 0;
}
2. Funções Virtuais (virtual)
(1) 2.1 Uso Básico
Declare uma função como virtual na classe base, e faça override na classe derivada.
#include <iostream>
#include <string>
class Animal {
protected:
std::string name;
public:
Animal(const std::string& n) : name(n) {}
// Declarar como função virtual
virtual void speak() {
std::cout << name << " faz um som." << std::endl;
}
// A classe base também deve ter um destrutor virtual!
virtual ~Animal() {}
};
class Dog : public Animal {
public:
Dog(const std::string& n) : Animal(n) {}
void speak() override {
std::cout << name << " diz: Au!" << std::endl;
}
};
int main() {
Animal* a = new Dog("Buddy");
a->speak(); // ✅ Saída: Buddy diz: Au!
delete a; // ✅ Chamará o destrutor de Dog primeiro, depois o de Animal
return 0;
}
⚠️ Ponto Importante: Quando uma classe base tem funções virtuais, o destrutor também deve ser declarado virtual! Caso contrário, delete em um ponteiro da classe base não chamará o destrutor da classe derivada.
(2) 2.2 A Palavra-chave override (C++11)
O C++11 introduziu a palavra-chave override, que permite ao compilador verificar se você está realmente sobrescrevendo uma função virtual.
class Dog : public Animal {
public:
// ✅ Com override, o compilador verificará
void speak() override { ... }
// ❌ Se a assinatura estiver errada (ex.: void speak(int x)), o compilador dará erro
// void speak(int x) override { ... } // Erro de compilação: nenhuma função para sobrescrever
};
💡 Recomendação: Sempre use override — deixe o compilador capturar bugs para você.
3. Funções Virtuais Puras e Classes Abstratas
(1) 3.1 O que é uma Função Virtual Pura?
Uma função virtual pura é uma função virtual que é apenas declarada mas não implementada na classe base, marcada com = 0.
class Animal {
public:
// Função virtual pura
virtual void speak() = 0;
virtual ~Animal() {}
};
Uma classe com funções virtuais puras é chamada de "classe abstrata" — não pode ser instanciada e só pode servir como classe base.
Animal a; // ❌ Erro de compilação: não é possível instanciar classe abstrata
Animal* p = new Dog("Buddy"); // ✅ Pode usar um ponteiro da classe base para apontar para uma classe derivada
(2) 3.2 Por que Precisamos de Funções Virtuais Puras?
Para forçar as classes derivadas a sobrescreverem a função.
class Animal {
public:
virtual void speak() = 0; // Forçar classes derivadas a implementar
};
class Dog : public Animal {
public:
void speak() override {
std::cout << "Au!" << std::endl;
}
// ✅ Se você esquecer de sobrescrever speak(), o compilador dará erro
};
4. Como as Funções Virtuais Funcionam (Opcional)
(1) 4.1 Tabela de Funções Virtuais (vtable)
C++ implementa polimorfismo usando uma tabela de funções virtuais (vtable):
- Para classes com funções virtuais, o compilador gera uma
vtable(uma tabela que armazena endereços de funções virtuais) - Cada objeto contém um
vptroculto (um ponteiro para a vtable) - Quando uma função virtual é chamada, o endereço correto da função é encontrado através do
vptr
💡 Dica: É por isso que objetos de classes com funções virtuais ocupam espaço extra de um ponteiro (8 bytes em sistemas de 64 bits).
5. Prática: Cálculo de Área de Formas Geométricas
▶ Exemplo 1: Usando Polimorfismo para Calcular Áreas de Diferentes Formas (Dificuldade ⭐⭐)
#include <iostream>
#include <vector>
#include <cmath>
// Classe base abstrata
class Shape {
public:
virtual double area() = 0;
virtual void printInfo() = 0;
virtual ~Shape() {}
};
// Círculo
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() override {
return 3.14159 * radius * radius;
}
void printInfo() override {
std::cout << "Círculo, raio = " << radius
<< ", área = " << area() << std::endl;
}
};
// Retângulo
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() override {
return width * height;
}
void printInfo() override {
std::cout << "Retângulo, " << width << " x " << height
<< ", área = " << area() << std::endl;
}
};
int main() {
std::vector<Shape*> shapes;
shapes.push_back(new Circle(5.0));
shapes.push_back(new Rectangle(4.0, 6.0));
for (Shape* s : shapes) {
s->printInfo();
}
// Liberar memória
for (Shape* s : shapes) {
delete s;
}
return 0;
}
Saída:
Círculo, raio =
Retângulo, x
❓ Perguntas Frequentes
P: Por que o destrutor deve ser virtual se há funções virtuais? R: Se você fizer
deleteem um ponteiro da classe base apontando para um objeto da classe derivada, e o destrutor da classe base não for virtual, apenas o destrutor da classe base será chamado, causando vazamento de recursos na classe derivada.CPPAnimal* a = new Dog("Buddy"); delete a; // ❌ Se ~Animal() não for virtual, o destrutor de Dog não será chamadoRegra de Ouro: Se uma classe base tem funções virtuais,
~BaseClass()deve servirtual.
P: Qual a diferença entre override e final? R: > -
override: diz ao compilador que esta função sobrescreve uma função virtual da classe base (deixa o compilador verificar para você) > -final: diz ao compilador que esta função não pode ser sobrescrita (usada em classes derivadas)CPPclass Dog : public Animal { public: void speak() override final { ... } // final: classes derivadas não podem mais sobrescrever speak() };
P: Quando devo usar polimorfismo? R: Quando precisar tratar diferentes tipos de objetos através de uma interface unificada.
Cenários típicos:
- Jogos: todos os personagens herdam de
Character, gerenciados uniformemente comCharacter*- GUI: todos os controles herdam de
Widget, desenhados uniformemente comWidget*- Sistemas de arquivos: todos os arquivos herdam de
File, lidos/escritos uniformemente comFile*
▶ Exemplo 3: Polimorfismo com Função Virtual (Dificuldade ⭐)
#include <iostream>
#include <string>
class Shape {
public:
virtual double area() { return 0; }
virtual ~Shape() {}
};
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() override { return 3.14 * radius * radius; }
};
int main() {
Shape* s = new Circle(2.0);
std::cout << "Produto de superfície: " << s->area() << std::endl;
delete s;
return 0;
}
Saída:
Produto de superfície:
virtual para declarar funções virtuais e override para indicar explicitamente a sobrescrita. Um ponteiro da classe base chamando um método da classe derivada é polimorfismo.
- Polimorfismo permite que ponteiros da classe base chamem a implementação correta da classe derivada
- Use
virtualna classe base para declarar funções virtuais - Use
overridena classe derivada (deixe o compilador verificar para você) - Se uma classe tem funções virtuais, seu destrutor também deve ser
virtual - Funções virtuais puras (
= 0) forçam classes derivadas a sobrescrever - Classes com funções virtuais puras são classes abstratas e não podem ser instanciadas
📖 Resumo
- Polimorfismo: ponteiro da classe base chama métodos da classe derivada
- virtual: declara funções virtuais
- override: indica explicitamente a sobrescrita de um método da classe base
- Destrutor virtual: garante a limpeza adequada dos recursos da classe derivada
📝 Exercícios
-
Básico (Dificuldade ⭐): Defina uma classe base
Vehiclecom uma função virtual puravoid move(). FaçaCareBicycleherdarem dela, exibindo "O carro está dirigindo na estrada" e "A bicicleta está pedalando na ciclovia" respectivamente. -
Intermediário (Dificuldade ⭐⭐): Estenda o programa acima adicionando uma função virtual
void stop(), comCarexibindo "O carro encosta" eBicycleexibindo "A bicicleta freia". -
Desafio (Dificuldade ⭐⭐⭐): Projete um sistema de salário de funcionários:
-
Classe base abstrata
Employee(nome, número de funcionário,virtual double calcSalary() = 0) -
FullTimeEmployee(salário mensal) -
PartTimeEmployee(salário por hora × horas trabalhadas) -
Use
vector<Employee*>para armazenar todos os funcionários e chamecalcSalary()uniformemente -
Funções virtuais: declaradas na classe base, sobrescritas na classe derivada, polimorfismo em tempo de execução
- A palavra-chave virtual declara funções virtuais
- override verifica a sobrescrita correta
- Funções virtuais puras e classes abstratas não podem ser instanciadas
- Tabela de funções virtuais (vtable) implementa polimorfismo em tempo de execução
6. 🚀 Próximos Passos
Agora que você aprendeu polimorfismo, vamos avançar para Templates (Aula 33) — fazendo funções e classes suportarem tipos arbitrários para verdadeira "programação genérica"!