¿Qué es el polimorfismo paramétrico en C++?

manueldemeza 09/11/2025

Ingeniería en comunicaciones y electrónica
Tema 3.1.1 del Plan de Estudios de POO IPN

¡Hi STEAMdiantes!, continuando en el ecosistema de C++ he visto a muchos estudiantes dominar el polimorfismo tradicional, aquel de la herencia y las funciones virtuales, el que llamamos polimorfismo de subtipo. Es el que permite que un puntero a una clase Vehiculo pueda apuntar a un Coche o a una Moto y ejecutar el método arrancar() correcto.

Pero hay otro tipo de polimorfismo, uno más sutil, que no ocurre en tiempo de ejecución, sino en tiempo de compilación. Un tipo de polimorfismo que no se basa en jerarquías de clases, sino en la reutilización de algoritmos para diferentes tipos de datos.

Hablamos del polimorfismo paramétrico, mejor conocido en el mundo de C++ como plantillas (templates) o, en un sentido más amplio, programación genérica.

¿Qué es el polimorfismo paramétrico?

Imagina que no estás escribiendo código, sino diseñando un contenedor físico, una caja de alta tecnología.

El polimorfismo de subtipo (el tradicional) sería como diseñar un “estacionamiento” que tiene espacios de diferentes formas: un espacio para “motos”, uno para “coches”, uno para “camiones”. Todos son “vehículos”, pero cada uno tiene su forma específica.

El polimorfismo paramétrico es diferente. Es como diseñar un solo tipo de caja mágica. Esta caja tiene la misma lógica de “abrir”, “cerrar” y “almacenar”, sin importar lo que pongas dentro. La caja en sí es genérica.

Tú, como usuario, decides qué contendrá en el momento de usarla:

  • “Quiero una caja mágica para guardar manzanas.”
  • “Quiero una caja mágica para guardar herramientas.”

La lógica de la caja (el algoritmo) es la misma. Lo único que cambia es el parámetro del tipo de contenido (manzanas o herramientas).

Eso es exactamente el polimorfismo paramétrico: Escribimos una “plantilla” de código (la caja) y el compilador crea una versión especializada de esa plantilla (la caja de manzanas) cuando le decimos qué tipo queremos usar.

¿Para qué sirve el polimorfismo paramétrico?

El polimorfismo paramétrico nos permite definir funciones y clases que operan sobre tipos de datos genéricos, especificados como “parámetros”. En C++, esto se logra con la palabra clave template.

Lo fundamental es entender que esto es polimorfismo en tiempo de compilación (compile-time).

A diferencia del polimorfismo de subtipo, que usa enlace dinámico (v-tables) para decidir qué función virtual llamar en tiempo de ejecución, el polimorfismo paramétrico se resuelve en la compilación. El compilador ve la plantilla y el tipo que estás usando, y literalmente escribe (instancia) una nueva versión de esa función o clase solo para ese tipo.

Funciones plantilla (template functions)

Son la forma más simple. Imagina que necesitas una función que encuentre el mayor de dos números. Podrías escribir una para int, otra para float, otra para double… o podrías usar una plantilla.

Código de ejemplo:

// 1. Definimos la 'plantilla'
template <typename T>
T obtenerMayor(T a, T b) {
    // La lógica es genérica y funciona mientras
    // el tipo 'T' soporte el operador '>'
    return (a > b) ? a : b;
}

// 2. Uso e instanciación
int main() {
    int i_max = obtenerMayor(10, 5);       // El compilador instancia obtenerMayor<int>
    float f_max = obtenerMayor(3.14f, 2.71f); // El compilador instancia obtenerMayor<float>
    
    // Esto fallaría en la compilación, porque el tipo 'std::string'
    // no tiene un operador '>' definido por defecto de esta manera.
    // std::string s_max = obtenerMayor("Hola", "Mundo"); 
    
    return 0;
}

La ventaja es que escribes el algoritmo una sola vez. El compilador hace el trabajo de crear las versiones específicas, garantizando seguridad de tipos (type safety) sin costo en tiempo de ejecución. La llamada a obtenerMayor(10, 5) es tan rápida como si hubieras escrito una obtenerMayor_int(int a, int b).

Clases plantilla (template classes)

Aquí es donde brilla el concepto. La biblioteca estándar de C++ (STL) se basa casi por completo en esto. std::vector, std::map, std::list… todos son clases plantilla.

Un std::vector<int> y un std::vector<double> son dos tipos completamente diferentes, generados a partir de la misma plantilla std::vector<T>.

Aplicaciones

Ahora, pensando en proyectos. No estamos solo para imprimir en consola; necesitamos rendimiento.

Supongamos que estamos diseñando el firmware para un robot y necesitamos una cola de mensajes (Queue) para comunicar tareas (por ejemplo, entre un hilo que lee sensores y un hilo que controla motores).

En lugar de escribir una ColaDeComandosDeMotor y una ColaDeLecturasDeSensor, creamos una plantilla genérica:

#include <deque> // Usaremos deque como base para la simplicidad
#include <mutex>

// Estructuras de datos específicas de nuestra aplicación
struct LecturaSensor {
    int id_sensor;
    float valor;
};

struct ComandoMotor {
    int id_motor;
    float velocidad;
    float angulo;
};

// Clase Plantilla: Nuestra Cola Segura (Thread-Safe)
template <typename T>
class ColaSegura {
private:
    std::deque<T> cola;
    std::mutex mtx; // Para proteger el acceso concurrente

public:
    void encolar(const T& item) {
        std::lock_guard<std::mutex> lock(mtx);
        cola.push_back(item);
        // ... aquí podríamos notificar a un hilo dormido ...
    }

    bool desencolar(T& item) {
        std::lock_guard<std::mutex> lock(mtx);
        if (cola.empty()) {
            return false;
        }
        item = cola.front();
        cola.pop_front();
        return true;
    }

    bool estaVacia() {
        std::lock_guard<std::mutex> lock(mtx);
        return cola.empty();
    }
};

// --- En nuestro main.cpp (o donde se inicialice el sistema) ---
// 3. Instanciamos las versiones especializadas
ColaSegura<LecturaSensor> cola_sensores;
ColaSegura<ComandoMotor> cola_motores;

int main() {
    // El hilo del sensor hace esto:
    cola_sensores.encolar({1, 9.81f});

    // El hilo del controlador de motores hace esto:
    cola_motores.encolar({0, 1.0f, 45.0f});

    // Y el hilo consumidor saca el dato:
    ComandoMotor cmd;
    if (cola_motores.desencolar(cmd)) {
        // ... mover el motor ...
    }
    
    return 0;
}

Hemos escrito una ColaSegura<T> una sola vez, y la estamos reutilizando para dos tipos de datos completamente diferentes. El código es limpio, mantenible y, lo más importante, seguro en tipos. El compilador no me permitirá meter un LecturaSensor en la cola_motores.

Pro tip

Es importante darles la teoría completa. Como profesional, debo darles la advertencia práctica:

El polimorfismo paramétrico en C++ tiene un costo: la inflación de código (code bloat).

Dado que el compilador crea una copia completa del código de la plantilla para cada tipo que usas (ColaSegura<int>, ColaSegura<float>, ColaSegura<ComandoMotor>), si no tienes cuidado, el tamaño de tu archivo binario puede crecer exponencialmente.

En un PC con gigabytes de RAM, esto es irrelevante. En un microcontrolador STM32 con 512KB de memoria Flash, es un problema crítico.

El polimorfismo paramétrico (plantillas) es una herramienta de abstracción increíblemente poderosa. Nos permite escribir algoritmos genéricos, reutilizables y con seguridad de tipos, cuyo costo de abstracción se paga en la compilación, no en la ejecución.

Polimorfismo de subtipo (virtual): diferentes comportamientos para la misma interfaz en tiempo de ejecución.

Polimorfismo paramétrico (templates): mismo comportamiento para diferentes tipos en tiempo de compilación.

En la ingeniería, usamos las plantillas con precisión quirúrgica: para crear contenedores eficientes, filtros digitales genéricos y sistemas de comunicación entre tareas, pero siempre vigilando el tamaño de nuestro binario.

Gracias por leernos.
Si te gusto este artículo, únete a nuestra comunidad en Facebook o WhatsApp para más…
¡Hasta la próxima!

Etiquetas:
¿Te gustó este artículo? ¡Compártelo!

Comentarios

0 0 votes
Valora este artículo
Suscribirse
Notificarme de
guest
0 Comentarios
Oldest
Newest Most Voted
Inline Feedbacks
View all comments