El camino de un ciclo
El código necesario para recorrer un vector de enteros y, por ejemplo, sumarle dos a cada uno de los valores que contiene ha sufrido grandes cambios a lo largo del tiempo.
Este es quizás uno de los ejemplos que demuestran la continua evolución del lenguaje. Veamos pues, un poco de historia.
El legado de C
C++ tomo sus raíces en C. Aun en estos días, la compatibilidad con código C es uno de los objetivos del comité para el estándar, aunque ultimamente han dejado de ser tan estrictos al respectos. En los primeros días de vida del lenguaje, los programadores hubieran escrito:
int * v = new int[N];
...
for( int k = 0; k < N ; k++ )
{
v[k] += 2;
}
Aparición de la STL
Con la incorporación de templates, C++ fue capaz de crear contenedores genéricos para darle al programador herramientas de alto nivel que producían código mas seguro y mantenible. Con el tiempo, el uso de templates se fue perfección y dio lugar a la creación de la STL (Standard Template Library), una colección de contenedores genéricos incluyendo vectores, listas, conjuntos y mapeos. Ya hablaremos de la STL en su momento, una pieza de diseño impresionante que vale la pena estudiar.
Cuando hizo su aparición, el ciclo for se transformo para siempre. Con la STL de nuestro lado, ahora la iteración anterior se codifica como:
typedef vector<int> container;
container v(N);
...
for( container::iterator i = v.begin(), i_end = v.end();
i != i_end; i++ )
{
*i += 2;
}
Usando algoritmos
Si alguien compara la iteración de nuestro nuevo vector con el ciclo for original, a simple vista parecería ser que no hemos logrado nada. Lo que es peor, el nuevo código resulta menos legible y por lo tanto mas difícil de mantener. Si miramos mejor nos vamos a dar cuenta de que en realidad hemos dado un gran paso. Por ejemplo, ahora es posible cambiar el tipo de contenedor a una lista, y no tendríamos que modificar absolutamente nada del nuevo ciclo for. Este es uno de los principales beneficios de la programación genérica. Hay otras razones por las cuales el nuevo ciclo es un avance, en otro momento las analizaremos. De todas formas, ningún programador de C++ que conozca la stl codificaría el ciclo de esta forma. Una de los éxitos de la STL, es la introducción de algoritmos genéricos para trabajar con los contenedores. Usaremos uno de los algoritmos mas simples para reescribir nuestro ciclo for. Vamos a necesitar una función auxiliar para esto, que sera ejecutada en cada uno de los valores del contenedor.
void sumar_dos(int & numero)
{
numero += 2;
}
De ahora en mas, suponemos que ha sido definido un contenedor de enteros llamado v. El ciclo queda escrito como:
for_each( v.begin(), v.end(), sumar_dos );
Agregando flexibilidad
Ahora el código es mucho mas legible. Existe un problema con este
planteo, ¿Que pasa si queremos sumar tres en lugar de dos? ¿Que pasa si utilizamos punto flotante en lugar de enteros? La idea de crear una función auxiliar para cada caso no es aceptable. Introduzcamos un poco de flexibilidad mediante el uso de functores, objetos que definen el operador parentesis, operator() y que por lo tanto pueden ser utilizados como funciones. Además lo haremos genérico para no depender de un tipo de dato en particular.
template<class T>
Sumador
{
public:
Sumador(T sn) : n(sn) {}
void operator()(T & numero)
{
numero += n;
}
private:
T n;
};
Ahora el ciclo queda reescrito como:
for_each( v.begin(), v.end(), Sumador<int> (2) );
Programación funcional
Hemos llegado a un ciclo no solo legible, sino también flexible. Sin embargo, podemos mejorarlo aun mas. La pregunta es: ¿Existe alguna forma de evitar la creación de clases como el sumador? ¿Que pasa si ahora queremos realizar una resta, o una asignación, o cualquier otra función? En el planteo actual tendríamos que crear un functor para cada una de estas operaciones. La STL define los functores mas utilizados pero estos no cubren la totalidad de los casos. La respuesta a nuestro problema se encuentra en una de las características del paradigma funcional: el calculo lambda. La idea es crear funciones anónimas para ser consumidas por el ciclo. Utilizando una librería como Boost.Lambda nuestro ciclo for se puede transformar en:
for_each( v.begin(), v.end(), _1 += 2 );
Un ultimo paso
Con el nuevo estándar, C++ incorporara una nueva herramienta: los conceptos. Esto permitirá crear código genérico en forma mas sencilla y posibilitara la creación en la STL de algoritmos basados en rangos en lugar de en dos iteradores. En unos años, escribiremos nuestro ciclo de la siguiente forma:
for_each( v, _1 += 2 );
Ahora si, definitivamente hemos avanzado.