Menu
Coddy logo textTech

Polimorfismo: Compilación vs. Tiempo de ejecución

Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 56 de 104.

Polimorfismo significa "muchas formas" y es un concepto fundamental de la POO que permite que los objetos sean tratados de manera uniforme mientras se comportan de forma diferente. C++ admite dos tipos distintos de polimorfismo, cada uno resuelto en una etapa diferente de la ejecución del programa.

Polimorfismo en tiempo de compilación (también llamado polimorfismo estático) es resuelto por el compilador antes de que el programa se ejecute. El compilador determina exactamente qué función llamar basándose en la firma de la función. Esto incluye la sobrecarga de funciones y las plantillas:

void print(int x) { std::cout << "Integer: " << x << std::endl; }
void print(double x) { std::cout << "Double: " << x << std::endl; }

print(5);      // El compilador elige print(int)
print(3.14);   // El compilador elige print(double)

El polimorfismo en tiempo de ejecución (también llamado polimorfismo dinámico) se resuelve mientras el programa se está ejecutando. La decisión sobre qué función llamar depende del tipo de objeto real, no del tipo de puntero o referencia. Esto se logra a través de funciones virtuales:

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 en tiempo de ejecución: "Drawing circle"

El compromiso clave: el polimorfismo en tiempo de compilación tiene una sobrecarga nula en tiempo de ejecución, ya que las decisiones se toman durante la compilación, mientras que el polimorfismo en tiempo de ejecución añade un pequeño coste (búsqueda en la vtable) pero proporciona una mayor flexibilidad para trabajar con objetos cuyos tipos no se conocen hasta la ejecución.

challenge icon

Desafío

Fácil

Vamos a construir un sistema de calculadora que demuestre ambos tipos de polimorfismo uno al lado del otro. Crearás un sistema donde el polimorfismo en tiempo de compilación maneja diferentes tipos de entrada a través de la sobrecarga de funciones, mientras que el polimorfismo en tiempo de ejecución permite intercambiar dinámicamente diferentes estrategias de cálculo.

Organizarás tu código en tres archivos:

  • Calculator.h: Define una clase base Calculator que represente cualquier estrategia de cálculo:
    • Un método virtual calculate(int a, int b) que devuelva un int e imprima: Base calculation: <a> ? <b> (devolviendo 0)
    • Un destructor virtual
  • Operations.h: Define dos clases de calculadora derivadas que sobrescriban el comportamiento del cálculo:
    • Adder: Sobrescribe calculate() para imprimir Adding: <a> + <b> y devolver la suma
    • Multiplier: Sobrescribe calculate() para imprimir Multiplying: <a> * <b> y devolver el producto
    Ambas clases deben usar la palabra clave override.
  • main.cpp: Crea un sistema que muestre ambos tipos de polimorfismo. Lee dos entradas enteras (cada una en una línea separada).

    Primero, demuestra el polimorfismo en tiempo de compilación creando tres funciones display() sobrecargadas:

    • display(int x) imprime: Integer value: <x>
    • display(double x) imprime: Double value: <x>
    • display(const std::string& x) imprime: String value: <x>

    Luego, demuestra el polimorfismo en tiempo de ejecución creando un arreglo de punteros Calculator* que contenga un Calculator base, un Adder y un Multiplier. Recorre el arreglo y llama a calculate() en cada uno con tus valores de entrada, imprimiendo el resultado después de cada cálculo.

    Estructura tu salida de la siguiente manera:

    === Compile-Time Polymorphism ===
    <display outputs for int, double, string>
    
    === Runtime Polymorphism ===
    <calculate outputs with results>

    Para la sección de tiempo de compilación, llama a display() con la primera entrada como un entero, luego como un double (el mismo valor sumándole .5), luego como el string "Result". Limpia tus calculadoras asignadas dinámicamente cuando termines.

Por ejemplo, con las entradas 10 y 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

Observa cómo el compilador selecciona la sobrecarga de display() correcta basándose en el tipo de argumento (decisión en tiempo de compilación), mientras que el método calculate() correcto se determina por el tipo de objeto real en tiempo de ejecución a través del mecanismo de la vtable.

Hoja de referencia

C++ admite dos tipos de polimorfismo que se resuelven en diferentes etapas:

Polimorfismo en tiempo de compilación (polimorfismo estático) es resuelto por el compilador antes de la ejecución. Incluye la sobrecarga de funciones y las plantillas:

void print(int x) { std::cout << "Integer: " << x << std::endl; }
void print(double x) { std::cout << "Double: " << x << std::endl; }

print(5);      // El compilador elige print(int)
print(3.14);   // El compilador elige print(double)

Polimorfismo en tiempo de ejecución (polimorfismo dinámico) se resuelve durante la ejecución utilizando funciones virtuales. El tipo de objeto real determina qué función se llama:

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 en tiempo de ejecución: "Drawing circle"

Compromiso: El polimorfismo en tiempo de compilación no tiene sobrecarga en tiempo de ejecución, mientras que el polimorfismo en tiempo de ejecución añade un pequeño coste (búsqueda en la vtable) pero proporciona una mayor flexibilidad.

Pruébalo tú mismo

#include <iostream>
#include <string>
#include "Calculator.h"
#include "Operations.h"

// TODO: Create three overloaded display() functions:
// 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() {
    // Lee dos entradas enteras
    int a, b;
    std::cin >> a;
    std::cin >> b;
    
    // === Polimorfismo en tiempo de compilación ===
    std::cout << "=== Compile-Time Polymorphism ===" << std::endl;
    // TODO: Llama a display() con:
    // - a como un entero
    // - a como un double (súmale 0.5)
    // - la cadena "Result"
    
    
    std::cout << std::endl;
    
    // === Polimorfismo en tiempo de ejecución ===
    std::cout << "=== Runtime Polymorphism ===" << std::endl;
    // TODO: Crea un arreglo de punteros Calculator* con 3 elementos:
    // - un Calculator base
    // - un Adder
    // - un Multiplier
    
    // TODO: Recorre el arreglo, llama a calculate(a, b) en cada uno,
    // e imprime "Result: <return_value>" después de cada cálculo
    
    
    // TODO: Limpia la memoria asignada dinámicamente
    
    
    return 0;
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación Orientada a Objetos