Introducción: ¿Qué son los Generics en C++ y para qué sirven?
En el mundo de la programación, a menudo surge la tarea de escribir código que funcione igualmente bien con diferentes tipos de datos. Por ejemplo, una función de ordenación de arrays debe poder ordenar tanto números enteros como cadenas u objetos personalizados. Sin el mecanismo de programación genérica (generics), habría que escribir funciones separadas para cada tipo, lo que provoca duplicación de código, errores y complica el mantenimiento.
En C++, este problema se resuelve con plantillas (templates), una herramienta potente que permite crear funciones y clases genéricas. El concepto de generics en C++ se implementa precisamente a través de las plantillas. Estas permiten al programador definir un algoritmo o estructura de datos sin vincularse a un tipo concreto, sustituyendo el tipo en tiempo de compilación.
Principales ventajas del uso de plantillas:
- Reducción de la duplicación de código — una plantilla reemplaza múltiples funciones sobrecargadas.
- Seguridad de tipos — todas las comprobaciones de tipos se realizan en tiempo de compilación.
- Rendimiento — las plantillas no añaden sobrecarga en tiempo de ejecución, ya que el código se genera para cada tipo concreto por separado.
- Flexibilidad — se pueden crear construcciones genéricas complejas, incluyendo contenedores (STL) y algoritmos.
En este artículo, analizaremos en detalle cómo usar plantillas en C++, comenzando con ejemplos simples y terminando con técnicas avanzadas.
Fundamentos del trabajo con plantillas de funciones
La forma más sencilla de familiarizarse con los generics es escribir una función plantilla. Supongamos que necesitamos una función que encuentre el máximo de dos valores. Sin plantillas, habría que escribir sobrecargas para int, double, std::string, etc. Con plantillas, esto se hace una sola vez:
#include <iostream>#include <string>
// Plantilla de funcióntemplate <typename T>T max(T a, T b) { return (a > b) ? a : b;}
int main() { std::cout << max(10, 20) << std::endl; // int std::cout << max(3.14, 2.71) << std::endl; // double std::cout << max("apple", "orange") << std::endl; // const char* (comparación de cadenas) return 0;}Aquí, la palabra clave template declara una plantilla, y typename T (o class T) define el parámetro de tipo. El compilador genera automáticamente una versión de la función para cada tipo utilizado. Es importante señalar que los tipos deben soportar el operador >; de lo contrario, se producirá un error de compilación.
También se pueden especificar varios parámetros de tipo:
template <typename T1, typename T2>void printPair(T1 a, T2 b) { std::cout << "(" << a << ", " << b << ")" << std::endl;}
// UsoprintPair(42, "Hello"); // (42, Hello)Plantillas de clases: creación de contenedores universales
Las plantillas de clases permiten crear estructuras de datos genéricas; esta es la base de la biblioteca estándar de plantillas (STL). Consideremos un ejemplo simple de una clase plantilla para un array dinámico:
#include <iostream>#include <stdexcept>
template <typename T>class Array {private: T* data; size_t size;public: Array(size_t sz) : size(sz) { data = new T[size]; } ~Array() { delete[] data; } T& operator[](size_t index) { if (index >= size) { throw std::out_of_range("Index out of range"); } return data[index]; } size_t getSize() const { return size; }};
int main() { Array<int> intArray(5); // Array de números enteros Array<double> doubleArray(3); // Array de números con