Polimorfismo em compilação vs. 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 central da OOP que permite que os objetos sejam tratados uniformemente enquanto se comportam de maneiras diferentes. C++ oferece suporte a dois tipos distintos de polimorfismo, cada um resolvido em uma etapa diferente da execução do programa.
Polimorfismo em tempo de compilação (também chamado de polimorfismo estático) é resolvido pelo compilador antes que o programa seja 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 em 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"A principal contrapartida: o polimorfismo em tempo de compilação não tem nenhuma sobrecarga em tempo de execução, pois as decisões são tomadas durante a compilação, enquanto o polimorfismo em tempo de execução adiciona um pequeno custo (consulta à 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 demonstre os dois tipos de polimorfismo lado a lado. Você criará um sistema no qual o polimorfismo em tempo de compilação lida com diferentes tipos de entrada por meio da sobrecarga de funções, enquanto o polimorfismo em tempo de execução permite trocar dinamicamente diferentes estratégias de cálculo.
Você organizará seu código em três arquivos:
Calculator.h: Defina uma classe baseCalculatorque represente 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 sobrescrevam 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 demonstre os dois tipos de polimorfismo. Leia duas entradas inteiras (cada uma em uma linha separada).Primeiro, demonstre o polimorfismo em 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 em tempo de execução criando um array de ponteiros
Calculator*contendo umCalculatorbase, umAddere umMultiplier. Percorra o array e chamecalculate()em cada elemento 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 (o mesmo valor com .5 adicionado) e, por fim, como a string "Result". Libere as 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 por meio do mecanismo de vtable.
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) - prints "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;
// === Compile-Time Polymorphism ===
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;
// === Runtime Polymorphism ===
std::cout << "=== Runtime Polymorphism ===" << std::endl;
// TODO: Crie um array de ponteiros Calculator* com 3 elementos:
// - a base Calculator
// - um Adder
// - um Multiplier
// TODO: Percorra o array, chame calculate(a, b) em cada,
// 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 POO
Arquivos externosBuild e compilação em C++Arquivos de cabeçalho e arquivos-fonteNamespaces e escopoIntrodução à POO em C++Classes vs. objetosO ponteiro 'this'Métodos (funções membro)Atributos (membros de dados)Noções básicas de construtores e destruidoresRevisão - calculadora simples4Propriedades de Classe
Membros de Instância vs. EstáticosGetters e SettersFunções Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRevisão - Gerenciador de Contas Bancárias7Herança
Herança básicaNíveis de acesso da herançaOrdem de chamada de construtor e destrutorSobrescrita de métodosFunções virtuais e VTableHerança múltiplaHerança virtualRevisão - Hierarquia de funcionários10Visão geral da STL
Visão geral e filosofia da STLContêineres da STLIteradoresAlgoritmos da STLFunctors e expressões lambdaRecapitulação - Frequência de palavras13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonFactory e Abstract FactoryPadrão BuilderPadrão ObserverPadrão Strategy2Gerenciamento de Memória
Memória Stack vs. HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Ponteiros Inteligentes em C++RAII em C++Revisão - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de acesso em C++Especificadores de acesso em profundidadeOcultação de informaçõesStruct vs ClasseClasses aninhadas e internasRecapitulação - Sistema de registros de alunos8Polimorfismo
Polimorfismo em compilação vs. execuçãoSobrecarga de funçõesFunções virtuais revisadasFunções virtuais purasClasses abstratasDesign de interfaces em C++Conversão dinâmica e RTTIRecapitulação — Calculadora de formas11Conceitos Avançados de OOP
Composição vs. HerançaMixins via CRTPIdioma PimplApagamento de TiposClasses Enum e Tipagem ForteTratamento de Exceções em OOPHierarquias de Exceções Personalizadas14Padrões de Projeto — Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentoListas de Inicialização do ConstrutorConstrutores DelegantesAprofundamento em DestrutoresRegra dos Três / Cinco / ZeroRevisão - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de FluxoSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçõesTemplates de ClassesEspecialização de TemplatesTemplates VariádicosNoções Básicas de SFINAE e Traits de TiposRecapitulação - Contêiner GenéricoPratique por conta própria: Compilador de C++ online