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.
Desafío
FácilVamos 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 baseCalculatorque represente cualquier estrategia de cálculo:- Un método virtual
calculate(int a, int b)que devuelva uninte imprima:Base calculation: <a> ? <b>(devolviendo 0) - Un destructor virtual
- Un método virtual
Operations.h: Define dos clases de calculadora derivadas que sobrescriban el comportamiento del cálculo:Adder: Sobrescribecalculate()para imprimirAdding: <a> + <b>y devolver la sumaMultiplier: Sobrescribecalculate()para imprimirMultiplying: <a> * <b>y devolver el producto
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 unCalculatorbase, unAddery unMultiplier. Recorre el arreglo y llama acalculate()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: 30Observa 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;
}
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
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genérico