Polimorfismo de Compilação vs Tempo de Execução
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 56 de 104.
Polimorfismo significa "muitas formas" e é um conceito fundamental de POO que permite que objetos sejam tratados de forma uniforme enquanto se comportam de maneira diferente. O C++ suporta dois tipos distintos de polimorfismo, cada um resolvido em um estágio diferente da execução do programa.
Polimorfismo de tempo de compilação (também chamado de polimorfismo estático) é resolvido pelo compilador antes de o programa ser executado. O compilador determina exatamente qual função chamar com base na assinatura da função. Isso inclui sobrecarga de funções e templates:
void print(int x) { std::cout << "Integer: " << x << std::endl; }
void print(double x) { std::cout << "Double: " << x << std::endl; }
print(5); // O compilador escolhe print(int)
print(3.14); // O compilador escolhe print(double)Polimorfismo de tempo de execução (também chamado de polimorfismo dinâmico) é resolvido enquanto o programa está em execução. A decisão sobre qual função chamar depende do tipo real do objeto, não do tipo do ponteiro ou da referência. Isso é alcançado por meio de funções virtuais:
class Shape {
public:
virtual void draw() { std::cout << "Drawing shape" << std::endl; }
};
class Circle : public Shape {
public:
void draw() override { std::cout << "Drawing circle" << std::endl; }
};
Shape* s = new Circle();
s->draw(); // Decidido em tempo de execução: "Drawing circle"O principal compromisso: o polimorfismo de tempo de compilação tem zero sobrecarga em tempo de execução, já que as decisões são tomadas durante a compilação, enquanto o polimorfismo de tempo de execução adiciona um pequeno custo (busca na vtable), mas oferece maior flexibilidade para trabalhar com objetos cujos tipos não são conhecidos até a execução.
Desafio
FácilVamos construir um sistema de calculadora que demonstra ambos os tipos de polimorfismo lado a lado. Você criará um sistema onde o polimorfismo de tempo de compilação lida com diferentes tipos de entrada através da sobrecarga de funções, enquanto o polimorfismo de tempo de execução permite que diferentes estratégias de cálculo sejam trocadas dinamicamente.
Você organizará seu código em três arquivos:
Calculator.h: Defina uma classe baseCalculatorque representa qualquer estratégia de cálculo:- Um método virtual
calculate(int a, int b)que retorna uminte imprime:Base calculation: <a> ? <b>(retornando 0) - Um destrutor virtual
- Um método virtual
Operations.h: Defina duas classes de calculadora derivadas que sobrescrevem o comportamento de cálculo:Adder: Sobrescrevacalculate()para imprimirAdding: <a> + <b>e retornar a somaMultiplier: Sobrescrevacalculate()para imprimirMultiplying: <a> * <b>e retornar o produto
override.main.cpp: Crie um sistema que mostre ambos os tipos de polimorfismo. Leia duas entradas inteiras (cada uma em uma linha separada).Primeiro, demonstre o polimorfismo de tempo de compilação criando três funções
display()sobrecarregadas:display(int x)imprime:Integer value: <x>display(double x)imprime:Double value: <x>display(const std::string& x)imprime:String value: <x>
Em seguida, demonstre o polimorfismo de tempo de execução criando um array de ponteiros
Calculator*contendo uma baseCalculator, umAddere umMultiplier. Percorra o array e chamecalculate()em cada um com seus valores de entrada, imprimindo o resultado após cada cálculo.Estruture sua saída da seguinte forma:
=== Compile-Time Polymorphism === <display outputs for int, double, string> === Runtime Polymorphism === <calculate outputs with results>Para a seção de tempo de compilação, chame
display()com a primeira entrada como um inteiro, depois como um double (mesmo valor com .5 adicionado) e, em seguida, como a string "Result". Limpe suas calculadoras alocadas dinamicamente quando terminar.
Por exemplo, com as entradas 10 e 3:
=== Compile-Time Polymorphism ===
Integer value: 10
Double value: 10.5
String value: Result
=== Runtime Polymorphism ===
Base calculation: 10 ? 3
Result: 0
Adding: 10 + 3
Result: 13
Multiplying: 10 * 3
Result: 30Observe como o compilador seleciona a sobrecarga correta de display() com base no tipo do argumento (decisão em tempo de compilação), enquanto o método calculate() correto é determinado pelo tipo real do objeto em tempo de execução através do mecanismo vtable.
Folha de consulta
O C++ suporta dois tipos de polimorfismo resolvidos em diferentes estágios:
Polimorfismo de tempo de compilação (polimorfismo estático) é resolvido pelo compilador antes da execução. Inclui sobrecarga de funções e templates:
void print(int x) { std::cout << "Integer: " << x << std::endl; }
void print(double x) { std::cout << "Double: " << x << std::endl; }
print(5); // O compilador escolhe print(int)
print(3.14); // O compilador escolhe print(double)Polimorfismo de tempo de execução (polimorfismo dinâmico) é resolvido durante a execução usando funções virtuais. O tipo real do objeto determina qual função é chamada:
class Shape {
public:
virtual void draw() { std::cout << "Drawing shape" << std::endl; }
};
class Circle : public Shape {
public:
void draw() override { std::cout << "Drawing circle" << std::endl; }
};
Shape* s = new Circle();
s->draw(); // Decidido em tempo de execução: "Drawing circle"Trade-off: O polimorfismo de tempo de compilação tem custo zero em tempo de execução, enquanto o polimorfismo de tempo de execução adiciona um pequeno custo (busca na vtable), mas oferece maior flexibilidade.
Experimente você mesmo
#include <iostream>
#include <string>
#include "Calculator.h"
#include "Operations.h"
// TODO: Crie três funções display() sobrecarregadas:
// 1. display(int x) - imprime "Integer value: <x>"
// 2. display(double x) - imprime "Double value: <x>"
// 3. display(const std::string& x) - imprime "String value: <x>"
int main() {
// Lê duas entradas inteiras
int a, b;
std::cin >> a;
std::cin >> b;
// === Polimorfismo em Tempo de Compilação ===
std::cout << "=== Compile-Time Polymorphism ===" << std::endl;
// TODO: Chame display() com:
// - a como um inteiro
// - a como um double (adicione 0.5 a ele)
// - a string "Result"
std::cout << std::endl;
// === Polimorfismo em Tempo de Execução ===
std::cout << "=== Runtime Polymorphism ===" << std::endl;
// TODO: Crie um array de ponteiros Calculator* com 3 elementos:
// - um Calculator base
// - um Adder
// - um Multiplier
// TODO: Percorra o array, chame calculate(a, b) em cada um,
// e imprima "Result: <return_value>" após cada cálculo
// TODO: Limpe a memória alocada dinamicamente
return 0;
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico