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.

11 comentarios:

fish dijo...

Desde argentina,te digo:seguí asi que vas bien,a pesar de que no entiendo nadaaaa de lo que decís.Por fin alguien que quiere laburar!!

eli dijo...

uyyyyyy amigo, te felicito, ardua tarea.te dejo un abrazo, adelante!!!!!!

BiGMaN dijo...

Mi conocimiento de STL llega hasta el 2° codigo... todo lo demas es "demasiado" para mi. De hecho, yo hubiera programado el codigo como el primero que mostraste. :lol:

Ahora, todo esta en el objetivo final del asunto.. claro si lo que se necesita es codigo reutilizable, mis soluciones son pauperrimas.

Una duda que siempre he tenido es que tan eficientes son los compiladores al momento de tranformar todo ese codigo en x86 o lo que sea. El generar tantos niveles de abstraccion no aumenta demasiado el payload si es que simplemente querias hacer una suma como el primer codigo?? o en tiempo de compilacion se genera un codigo especifico para cada una de las necesidades. Ej:

for_each( v, _1 += 2 );
for_each( v, _1 *= 2 );

El codigo final para cada una de esas llamadas es diferente, o es el mismo con condiciones y demas?

Matias Capeletto dijo...

En los compiladores modernos (VC8, gcc4, etc) la performance de todos estos ciclos es similar. Realmente hacen maravillas los compiladores :)

Zordial Qulog dijo...

Hi Matias

There's a small error in this example:

for_each( v.begin(), v.end(), Sumador(2) );

It should be like this:

for_each( v.begin(), v.end(), Sumador<int>(2) );

Best

P.S: Also, a function name denotes its address, so you don't need "&" to take the address of a function.

P.S. 2: I promise I'll fully review Boost.Bimap when it gets formally reviewed. Really.

Matias Capeletto dijo...

Estaba utilizando los simbolos mayor y menor directamente en el html, ahora esta arreglado.
Espero tus comentarios sobre Boost.Bimap!

Unknown dijo...

Te jodo otra vez. Hace poco leí una entrevista al creador de la STL. El tipo parecía que no sabía que existían los métodos abstractos de Smalltalk ni el double dispatching. Los métodos abstractos pertenecen a clases abstractas que se ligan en tiempo de ejecución dinámicamente. Para resolver la cuestión de los muchos argumentos (mas de 5, aunque pocas veces pasa), se pasa una colección de argumentos. El tema de los diferentes "tipos" se resuelve con doble despacho de mensajes.
Mi primer pregunta sería entonces ¿no creés que STL es un atraso en comparación a la NIHCL?

Tengo la intención de programar con STL o Boost... pero están TAN orientadas a algoritmos y verborragia léxica (para mi que estoy acostumbrado a escribir poco) que es un poco molesto.
Mi segunda pregunta entonces ¿no existen mini-aplicaciones "reales" (no ejemplos de tutorial) de código que use la STL? Los ejemplos que ví hasta ahora son demasiado de explicación para qué sirve, pero no los ví aplicados... :(

Matias Capeletto dijo...

No creo que sea un atraso. Son caminos diferentes, compromisos diferentes. Smalltalk y C++ atacan los problemas desde dos puntos de vista diferentes. Hay cosas que en Smalltalk pueden resultar mucho mas sencillas pero todo tiene su precio.
C++ solo utilizando la STL carece de muchas herramientas básicas. Ahi es donde entra Boost al rescate. Mucha de la verborragia va a ser eliminada con C++0x.

Con respecto a algún lugar donde ver código que utilice la STL, yo leeria la documentación de las librerías de Boost.
Ahí tenes para un largo rato :)

zXc dijo...

buy bentyl
buy Diarex

Luis dijo...

Tablet PC

Manual Samsung Galaxy SII

Nokia Phoenix 2011

Sony ericsson txt pro dijo...

Bastante instructivo, gracias