Skip to content

[Boost en español] Ejemplo rápido de aplicación de Bind

15 julio 2010

Justo ayer, peleándome un rato con freegemas, me surgió la necesidad de iterar por un vector y borrar los elementos que tuvieran cierta propiedad. A primera vista puede parecer sencillo, pero la solución no es trivial:

  • Si utilizas un bucle for, al borrar un elemento el tamaño y los índices de los elementos también, por lo que la variable de seguimiento (típicamente size_t i) ya no vale.
  • Si utilizas iteradores, al eliminar un elemento aquellos quedan invalidados.

Estuve pensando un poco cómo hacerlo y se me ocurrieron algunas formas poco ortodoxas o que podrían llevar a error. Una de las formas que encontré por internet fue la siguiente, con iteradores y un for algo especial:

for (iterator it = v.begin(); 
     it != v.end(); 
    ) 
{ 
    if (condition) 
	it = v.erase(it); 
    else 
	++it; 
}

Si os fijáis, el for no tiene tercer parámetro, sino que somos nosotros los que movemos el iterador manualmente. Para evitar la invalidación, utilizamos el valor de retorno de erase.

Pero otra forma de hacerlo, más elegante a mi parecer, era utilizar el algoritmo remove_if y un predicado adecuado. En mi caso, el contenedor tenía objetos de una clase con un método que devolvía un booleano tal que, si es verdadero, el elemento debería borrarse. En un primer intento hice

remove_if(contenedor.begin(), contenedor.end(), boost::bind<bool>(&Clase::Comprobar, _1));

Pero los elementos no se borraban. «Qué raro», pensé. Resulta que el funcionamiento de remove_if no es el de borrar directamente los elementos, sino que reordena los elementos a borrar al final del contenedor y devuelve un iterador que apunta al primero de esos elementos a borrar. Así, junto al método erase del vector, tenemos:

contenedor.erase(remove_if(contenedor.begin(), contenedor.end(), boost::bind<bool>(&Clase::Comprobar, _1)), contenedor.end());

Esto se conoce como el Erase-remove idiom y tiene hasta su propio artículo en la Wikipedia. Muy curioso.

One Comment leave one →
  1. 15 julio 2010 16:08

    Toda una maniobra ninja, sí señor. Me he enfrentado a este problema en un par de ocasiones y no he encontrado una solución tan buena como la tuya.

    En Granny’s Bloodbath la clase que lleva la lógica de juego almacena listas de punteros a Actors (según a la familia de actores a la que pertenecen: enemigos, items o balas). En cada iteración del main loop se comprueba si hay algún Actor cuyo estado sea Actor::ERASE y se borra de la lista. En caso de que dos actores deban ser eliminados a la vez hay problemas.

    Simplemente hice que sólo se pudiera eliminar un actor en cada iteración. Cuando el juego va a 30fps no se nota (es muy raro que esto ocurra) pero sigue siendo una solución inifitamente cutre.

    ¡Saludos!

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: