Menu
Coddy logo textTech

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.

challenge icon

Desafio

Fácil

Vamos 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 base Calculator que representa qualquer estratégia de cálculo:
    • Um método virtual calculate(int a, int b) que retorna um int e imprime: Base calculation: <a> ? <b> (retornando 0)
    • Um destrutor virtual
  • Operations.h: Defina duas classes de calculadora derivadas que sobrescrevem o comportamento de cálculo:
    • Adder: Sobrescreva calculate() para imprimir Adding: <a> + <b> e retornar a soma
    • Multiplier: Sobrescreva calculate() para imprimir Multiplying: <a> * <b> e retornar o produto
    Ambas as classes devem usar a palavra-chave 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 base Calculator, um Adder e um Multiplier. Percorra o array e chame calculate() 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: 30

Observe 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;
}
quiz iconTeste seus conhecimentos

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