la35.net blog para alumnos de la ET Nº35

Genéricos

Para empezar tomemos la definición de la “Guía de porgramación de C#” de Microsoft:

Los genéricos (generics) introducen el concepto de parámetros de tipo (typename) esto permite diseñar clases y métodos que aplazan la especificación de uno o varios tipos hasta que el código de cliente declare y cree una instancia de la clase o el método. Por ejemplo, al usar un parámetro de tipo genérico T, puede escribir una clase única que otro código de cliente puede usar sin incurrir en el costo o riesgo de conversiones en tiempo de ejecución u operaciones de conversión. [1]

Perfecto, si no entendiste nada aún, vamos con un ejemplo. Supongamos que tengo que definir una funcion que imprima por consola un valor de tipo string pasado como argumento. Entonces la codificación en C++ sería tal que así:

#include <iostream>
using namespace std;

void Imprimir(string valor)
{
    cout<<"La consola imprime el valor: "<<valor<<endl;
}

int main()
{
    Imprimir("Hola");
    return 0;
}

Bueno genial, nos imprime un tipo de dato string por consola, pero… ¿y si ahora quiero agregar una función que imprima un número? Supongamos un entero. Para ello habría que sobrecargar la función imprimir y quedaría de la siguiente manera:

#include <iostream>
using namespace std;

void Imprimir(string valor)
{
    cout<<"La consola imprime el valor: "<<valor<<endl;
}
void Imprimir(int valor)
{
    cout<<"La consola imprime el valor: "<<valor<<endl;
}

int main()
{
    Imprimir("Hola");
    Imprimir(4);
    return 0;
}

Espectacular! Pero… ¿y si tambien quiero usar la misma función para imprimir un tipo de dato float?¿Y también un double? Ah, y un char (ya que estamos). Bueno, se podrán imaginar que hay que seguir sobrecargando la misma función imprimir para cada tipo de dato. A esta altura, el código pasó a ser algo, no solo repetitivo sino que además poco eficiente. Sin embargo, es en este punto en el cual encontramos la solución a esto. Y si, son los generics. Para un código más limpio, desengrasado y sin manchas que quedaría de la siguiente manera:

#include <iostream>
using namespace std;

template<typename T>
void Imprimir(T valor)
{
    cout<<"La consola imprime el valor: "<<valor<<endl;
}
int main()
{
    Imprimir("Hola");
    Imprimir(4);
    Imprimir("a");
    Imprimir(4.25);
    return 0;
}

Fijense que agregamos la línea template<typename T> y quitamos la 2da definición del método Imprimir. Con esta línea le estamos especificando al compilador que hay un tipo genérico que nosotros bautizamos como T (letra utilizada por convención). Por lo tanto, cuando estemos en tiempo de ejecución, nuestro intérprete va a asignar al parámetro T el valor que corresponda para cada caso. En el ejemplo anterior vemos que primero asigna un tipo string, luego un tipo int, etc. Podemos observar que es una ventaja a la hora de definir un único comportamiento para diferentes tipos de datos.

Esta técnica se usa comunmente en colecciones como listas y diccionarios. Por ejemplo nosotros podemos tener una lista de letras, de palabras, de números tanto enteros como reales y también objetos. Sin embargo, el tipo de operaciones que hacemos sobre una lista son siempre los mismos: agregar, ordenar, insertar, eliminar, entre otras; y es por eso que repetir el código para cada tipo de dato resulta una solución un tanto pésima. Espero que llegado este punto hayamos podido entender por qué y para qué se usa esto. Me conformo con que sea útil para evolucionar como programadores (al menos mejores que hace 2 minutos).


Referencias

  1. Microsoft - Documentación Oficial
  2. The Cherno - Templates in C++
  3. Generics in C# - Unity
  4. What are generics? C# - Unity