Introducción: qué son los generics en C++ y para qué sirven
En el mundo de la programación moderna, la reutilización del código es uno de los principios clave. Si escribes una función de suma para int, y luego para double y float, tarde o temprano querrás evitar la duplicación. Aquí es donde los generics (programación genérica) vienen al rescate. En C++, este concepto se implementa a través de plantillas (templates).
Los generics permiten escribir código que funciona con cualquier tipo de datos, sin sacrificar el rendimiento (a diferencia de las funciones virtuales o void*). El compilador genera una versión separada de la función o clase para cada tipo específico, lo que proporciona la máxima velocidad de ejecución.
En este artículo, analizaremos para qué sirven los generics en C++, cómo declararlos y usarlos correctamente en la práctica, así como los errores típicos y las técnicas avanzadas.
Fundamentos de sintaxis: cómo declarar y usar una plantilla
Una plantilla en C++ se declara con la palabra clave template y los corchetes angulares <>, dentro de los cuales se especifica el parámetro de tipo (generalmente typename o class).
Función plantilla
El ejemplo más simple es una función que devuelve el máximo de dos valores:
template <typename T>T max(T a, T b) { return (a > b) ? a : b;}
// Uso:int main() { std::cout << max(3, 7) << std::endl; // 7 std::cout << max(3.14, 2.71) << std::endl; // 3.14 std::cout << max('a', 'z') << std::endl; // z return 0;}Observa: el compilador deduce el tipo T automáticamente según los argumentos pasados. Si necesitas especificar el tipo explícitamente, puedes escribir max<double>(3.14, 2.71).
Clase plantilla
Las plantillas también funcionan con clases. Un ejemplo clásico es un contenedor simple "par":
template <typename T1, typename T2>class Pair {private: T1 first; T2 second;public: Pair(T1 f, T2 s) : first(f), second(s) {} T1 getFirst() const { return first; } T2 getSecond() const { return second; }};
// Uso:Pair<int, std::string> student(1, "Alice");std::cout << student.getFirst() << " " << student.getSecond();Aquí T1 y T2 son parámetros de plantilla que pueden ser cualquier tipo.
Ejemplos prácticos: desde contenedores simples hasta algoritmos
Veamos varios escenarios reales donde los generics son indispensables.
Ordenamiento universal
Escribamos una función plantilla para ordenar un arreglo de cualquier tipo que soporte el operador <:
template <typename T>void bubbleSort(T arr[], int n) { for (int i = 0; i < n - 1; ++i) for (int j = 0; j < n - i - 1; ++j) if (arr[j] > arr[j + 1]) std::swap(arr[j], arr[j + 1]);}
// Ejemplo:int intArr[] = {5, 2, 8, 1};bubbleSort(intArr, 4); // Funciona con int
double dblArr[] = {3.14, 2.71, 1.41};bubbleSort(dblArr, 3); // Funciona con doublePila con tipo de datos arbitrario
Implementemos una pila simple basada en plantillas:
template <typename T>class Stack {private: std::vector<T> elements;public: void push(const T& value) { elements.push_back(value); } void pop() { if (!elements.empty()) elements.pop_back(); } T top() const { return elements.back(); } bool empty() const { return elements.empty(); }};
// Uso:Stack<int> intStack;intStack.push(10);intStack.push(20);std::cout << intStack.top(); // 20Algoritmo "buscar y reemplazar"
Función plantilla que reemplaza todas las ocurrencias de un valor por otro en un contenedor:
templa