loop instead for based c++ stl foreach coding-style

instead - for each c++



Ventajas de std:: for_each over for loop (19)

Al igual que muchas de las funciones de algoritmo, una reacción inicial es pensar que es más ilegible utilizar foreach que un ciclo. Ha sido un tema de muchas guerras de fuego.

Una vez que te acostumbras a la expresión idiomática, puedes encontrarla útil. Una ventaja obvia es que obliga al codificador a separar los contenidos internos del bucle de la funcionalidad de iteración real. (OK, creo que es una ventaja. Otros dicen que estás cortando el código sin ningún beneficio real).

Otra ventaja es que cuando veo foreach, que cada elemento será procesado o se lanzará una excepción.

Un ciclo for permite varias opciones para terminar el ciclo. Puede dejar que el bucle ejecute su curso completo, o puede usar la palabra clave break para saltar explícitamente del bucle, o usar la palabra clave return para salir de la función completa mid-loop. En contraste, foreach no permite estas opciones, y esto lo hace más legible. Puede echar un vistazo al nombre de la función y conocer la naturaleza completa de la iteración.

Aquí hay un ejemplo de un bucle for confuso:

for(std::vector<widget>::iterator i = v.begin(); i != v.end(); ++i) { ///////////////////////////////////////////////////////////////////// // Imagine a page of code here by programmers who don''t refactor /////////////////////////////////////////////////////////////////////// if(widget->Cost < calculatedAmountSofar) { break; } //////////////////////////////////////////////////////////////////////// // And then some more code added by a stressed out juniour developer // *#&$*)#$&#(#)$#(*$&#(&*^$#(*$#)($*#(&$^#($*&#)$(#&*$&#*$#*)$(#* ///////////////////////////////////////////////////////////////////////// for(std::vector<widgetPart>::iterator ip = widget.GetParts().begin(); ip != widget.GetParts().end(); ++ip) { if(ip->IsBroken()) { return false; } } }

¿Hay alguna ventaja de std::for_each over for loop? Para mí, std::for_each solo parece dificultar la legibilidad del código. ¿Por qué entonces algunos estándares de codificación recomiendan su uso?


Aparte de la legibilidad y el rendimiento, un aspecto comúnmente ignorado es la consistencia. Hay muchas formas de implementar un bucle for (o while) sobre iteradores, desde:

for (C::iterator iter = c.begin(); iter != c.end(); iter++) { do_something(*iter); }

a:

C::iterator iter = c.begin(); C::iterator end = c.end(); while (iter != end) { do_something(*iter); ++iter; }

con muchos ejemplos en el medio con diferentes niveles de eficiencia y potencial de errores.

El uso de for_each, sin embargo, impone consistencia al abstraer el bucle:

for_each(c.begin(), c.end(), do_something);

Lo único que tiene que preocuparse ahora es: ¿implementa el cuerpo del bucle como función, un functor o un lambda usando las funciones de Boost o C ++ 0x? Personalmente, prefiero preocuparme por eso que por cómo implementar o leer un ciclo aleatorio para / while.


Aquí hay algunas razones:

  1. Parece dificultar la legibilidad simplemente porque no está acostumbrado y / o no está usando las herramientas adecuadas para hacerlo realmente fácil. (ver boost :: range y boost :: bind / boost :: lambda para ayudantes. Muchos de estos irán a C ++ 0x y harán más útiles para cada función y funciones relacionadas).

  2. Le permite escribir un algoritmo además de for_each que funciona con cualquier iterador.

  3. Reduce la posibilidad de errores de tipeo estúpidos.

  4. También abre tu mente al resto de los algoritmos STL, como find_if , sort , replace , etc. y estos ya no se verán tan extraños. Esto puede ser una gran victoria.

Actualización 1:

Lo más importante es que te ayuda a ir más allá de cada for_each de los bucles for-like que eso es todo, y mira los otros STL-alogs, como find / sort / partition / copy_replace_if, ejecución paralela ... o lo que sea.

Se puede escribir mucho procesamiento de forma muy concisa usando "el resto" de los hermanos de for_each, pero si todo lo que haces es escribir un ciclo forzado con varias lógicas internas, entonces nunca aprenderás cómo usarlos, y podrás terminan inventando la rueda una y otra vez.

Y (el próximo rango de estilo disponible para cada uno):

for_each(monsters, boost::mem_fn(&Monster::think));

O con C ++ x11 lambdas:

for_each(monsters, [](Monster& m) { m.think(); });

es IMO más legible que:

for(Monsters::iterator i = monsters.begin(); i != monsters.end(); ++i) { i->think(); }

También esto (o con lambdas, ver otros):

for_each(bananas, boost::bind(&Monkey::eat, my_monkey, _1));

Es más conciso que:

for(Bananas::iterator i = banans.begin(); i != bananas.end(); ++i) { my_monkey->eat(*i); }

Especialmente si tienes varias funciones para llamar en orden ... pero tal vez sea solo yo. ;)

Actualización 2 : he escrito mis propios envoltorios de una línea de stl-algos que funcionan con rangos en lugar de un par de iteradores. boost :: range_ex, una vez lanzado, incluirá eso y tal vez estará allí en C ++ 0x también?


Con C ++ 11 y dos plantillas simples, puede escribir

for ( auto x: range(v1+4,v1+6) ) { x*=2; cout<< x <<'' ''; }

como reemplazo de for_each o de un loop. Por qué elegir se reduce a brevedad y seguridad, no hay posibilidad de error en una expresión que no está allí.

Para mí, for_each siempre, for_each era mejor en el mismo terreno cuando el cuerpo del bucle ya es un funtor, y aprovecharé cualquier ventaja que pueda obtener.

Todavía usas la expresión for tres, pero ahora cuando ves una, sabes que hay algo que entender allí, no es una repetición. Odio la repetición. Me molesta su existencia. No es un código real, no hay nada que aprender al leerlo, es solo una cosa más que necesita ser revisada. El esfuerzo mental puede medirse por lo fácil que es oxidarse al verificarlo.

Las plantillas son

template<typename iter> struct range_ { iter begin() {return __beg;} iter end(){return __end;} range_(iter const&beg,iter const&end) : __beg(beg),__end(end) {} iter __beg, __end; }; template<typename iter> range_<iter> range(iter const &begin, iter const &end) { return range_<iter>(begin,end); }


Easy: for_each es útil cuando ya tienes una función para manejar cada elemento del array, por lo que no tienes que escribir un lambda. Ciertamente, esto

for_each(a.begin(), a.end(), a_item_handler);

es mejor que

for(auto& item: a) { a_item_handler(a); }

Además, el rango for ciclo solo itera sobre contenedores completos de principio a fin, mientras que para cada for_each es más flexible.


El bucle for_each está destinado a ocultar los iteradores (detalle de cómo se implementa un bucle) a partir del código de usuario y definir una semántica clara en la operación: cada elemento se repetirá exactamente una vez.

El problema con la legibilidad en el estándar actual es que requiere un functor como último argumento en lugar de un bloque de código, por lo que en muchos casos debe escribir un tipo de functor específico para él. Esto se convierte en un código menos legible ya que los objetos del funtor no se pueden definir in situ (las clases locales definidas dentro de una función no se pueden usar como argumentos de plantilla) y la implementación del bucle se debe alejar del bucle real.

struct myfunctor { void operator()( int arg1 ) { code } }; void apply( std::vector<int> const & v ) { // code std::for_each( v.begin(), v.end(), myfunctor() ); // more code }

Tenga en cuenta que si desea realizar una operación específica en cada objeto, puede usar std::mem_fn , o boost::bind ( std::bind en el siguiente estándar), o boost::lambda (lambdas en el siguiente estándar) para hacerlo más simple:

void function( int value ); void apply( std::vector<X> const & v ) { // code std::for_each( v.begin(), v.end(), boost::bind( function, _1 ) ); // code }

Que no es menos legible y más compacto que la versión enrollada a mano si tiene función / método para llamar en su lugar. La implementación podría proporcionar otras implementaciones del bucle for_each (piense en el procesamiento paralelo).

El próximo estándar se encarga de algunas de las deficiencias de diferentes maneras, permitirá que las clases definidas localmente sean argumentos para las plantillas:

void apply( std::vector<int> const & v ) { // code struct myfunctor { void operator()( int ) { code } }; std::for_each( v.begin(), v.end(), myfunctor() ); // code }

Mejorando la localidad del código: cuando navegas, ves lo que está haciendo ahí mismo. De hecho, ni siquiera necesita usar la sintaxis de clase para definir el funtor, pero use una lambda allí mismo:

void apply( std::vector<int> const & v ) { // code std::for_each( v.begin(), v.end(), []( int ) { // code } ); // code }

Incluso si para el caso de for_each habrá una construcción específica que lo hará más natural:

void apply( std::vector<int> const & v ) { // code for ( int i : v ) { // code } // code }

for_each a mezclar el constructo for_each con bucles enrollados a mano. Cuando solo necesito una llamada a una función o método existente ( for_each( v.begin(), v.end(), boost::bind( &Type::update, _1 ) ) ) voy por la construcción for_each que quita muchas cosas del iterador de la placa de la caldera al código. Cuando necesito algo más complejo y no puedo implementar un functor solo un par de líneas sobre el uso real, muevo mi propio bucle (mantiene la operación en su lugar). En secciones de código no críticas podría ir con BOOST_FOREACH (un compañero de trabajo me metió en ello)


En su mayoría es correcto: la mayoría de las veces, std::for_each es una pérdida neta. Yo iría tan lejos como para comparar for_each to goto . goto proporciona el control de flujo más versátil posible; puede usarlo para implementar virtualmente cualquier otra estructura de control que pueda imaginar. Esa gran versatilidad, sin embargo, significa que ver un goto en aislamiento no le dice prácticamente nada sobre lo que se pretende hacer en esta situación. Como resultado, casi nadie en su sano juicio usa goto excepto como último recurso.

Entre los algoritmos estándar, for_each es de la misma manera: se puede usar para implementar virtualmente cualquier cosa, lo que significa que ver for_each te dice prácticamente nada sobre para qué se usa en esta situación. Desafortunadamente, la actitud de la gente hacia for_each es acerca de dónde estaba su actitud hacia goto en (digamos) alrededor de 1970: algunas personas se habían dado cuenta de que solo debería usarse como último recurso, pero muchos todavía lo consideran el algoritmo principal. , y rara vez si alguna vez usa otro. La gran mayoría de las veces, incluso una mirada rápida revelaría que una de las alternativas era drásticamente superior.

Solo por ejemplo, estoy bastante seguro de haber perdido la noción de cuántas veces he visto a gente escribiendo códigos para imprimir los contenidos de una colección usando for_each . Según las publicaciones que he visto, este podría ser el uso más común de for_each . Terminan con algo como:

class XXX { // ... public: std::ostream &print(std::ostream &os) { return os << "my data/n"; } };

Y su publicación está preguntando qué combinación de bind1st , mem_fun , etc. necesitan para hacer algo como:

std::vector<XXX> coll; std::for_each(coll.begin(), coll.end(), XXX::print);

trabajar, e imprimir los elementos de coll . Si realmente funcionó exactamente como lo escribí allí, sería mediocre, pero no es así, y para cuando lo hayas hecho funcionar, es difícil encontrar esos pocos códigos relacionados con lo que pasando entre las piezas que lo mantienen unido.

Afortunadamente, hay una manera mucho mejor. Agregue una sobrecarga de insertador de flujo normal para XXX:

std::ostream &operator<<(std::ostream *os, XXX const &x) { return x.print(os); }

y use std::copy :

std::copy(coll.begin(), coll.end(), std::ostream_iterator<XXX>(std::cout, "/n"));

Eso funciona, y no requiere prácticamente ningún trabajo para darse cuenta de que imprime los contenidos de coll en std::cout .


En su mayoría, tendrá que iterar sobre toda la colección . Por lo tanto, le sugiero que escriba su propia variante for_each (), tomando solo 2 parámetros. Esto te permitirá reescribir el ejemplo de Terry Mahaffey como:

for_each(container, [](int& i) { i += 10; });

Creo que esto es de hecho más legible que un ciclo for. Sin embargo, esto requiere las extensiones del compilador C ++ 0x.


Encuentro que_ cada uno es malo para la legibilidad. El concepto es bueno, pero c ++ hace que sea muy difícil de leer, al menos para mí. Expresiones c ++ 0x lamda ayudarán. Realmente me gusta la idea de lamdas. Sin embargo, a primera vista, creo que la sintaxis es muy fea y no estoy 100% seguro de que alguna vez me acostumbraré. Quizás en 5 años me haya acostumbrado y no lo pensé un segundo, pero tal vez no. El tiempo dirá :)

Prefiero usar

vector<thing>::iterator istart = container.begin(); vector<thing>::iterator iend = container.end(); for(vector<thing>::iterator i = istart; i != iend; ++i) { // Do stuff }

Encuentro un explícito para borrar el bucle y la explicidad usando variables nombradas para los iteradores de inicio y final reduce el desorden en el bucle for.

Por supuesto, los casos varían, esto es justo lo que generalmente encuentro mejor.


La ventaja de escribir funcional para ser más legible, puede no aparecer cuando for(...) y para cada for_each(... ).

Si utiliza todos los algoritmos en functional.h, en lugar de usar for-loops, el código se vuelve mucho más legible;

iterator longest_tree = std::max_element(forest.begin(), forest.end(), ...); iterator first_leaf_tree = std::find_if(forest.begin(), forest.end(), ...); std::transform(forest.begin(), forest.end(), firewood.begin(), ...); std::for_each(forest.begin(), forest.end(), make_plywood);

es mucho más legible que;

Forest::iterator longest_tree = it.begin(); for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{ if (*it > *longest_tree) { longest_tree = it; } } Forest::iterator leaf_tree = it.begin(); for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{ if (it->type() == LEAF_TREE) { leaf_tree = it; break; } } for (Forest::const_iterator it = forest.begin(), jt = firewood.begin(); it != forest.end(); it++, jt++) { *jt = boost::transformtowood(*it); } for (Forest::const_iterator it = forest.begin(); it != forest.end(); ++it{ std::makeplywood(*it); }

Y eso es lo que creo que es tan bueno, generalizar los for-loops a funciones de una línea =)


Lo bueno de C++11 (anteriormente llamado C ++ 0x) es que este agotador debate se resolverá.

Quiero decir, nadie en su sano juicio, que quiera iterar sobre una colección completa, todavía usará esto

for(auto it = collection.begin(); it != collection.end() ; ++it) { foo(*it); }

O esto

for_each(collection.begin(), collection.end(), [](Element& e) { foo(e); });

cuando la sintaxis de bucle basada for rango está disponible:

for(Element& e : collection) { foo(e); }

Este tipo de sintaxis ha estado disponible en Java y C # desde hace algún tiempo, y en realidad hay mucho más bucles foreach que clásicos for bucles en cada código reciente de Java o C # que vi.


Personalmente, en cualquier momento que necesite salir de mi camino para usar std::for_each (escribir functors de propósito especial / complicado boost::lambda s), me parece que BOOST_FOREACH y C ++ 0x se basan en el rango para tener más claro:

BOOST_FOREACH(Monster* m, monsters) { if (m->has_plan()) m->act(); }

vs

std::for_each(monsters.begin(), monsters.end(), if_then(bind(&Monster::has_plan, _1), bind(&Monster::act, _1)));




Si utiliza con frecuencia otros algoritmos de STL, hay varias ventajas en for_each :

  1. A menudo será más simple y menos propenso a errores que un bucle for, en parte porque estarás acostumbrado a las funciones con esta interfaz, y en parte porque en realidad es un poco más conciso en muchos casos.
  2. Aunque un bucle for basado en rango puede ser incluso más simple, es menos flexible (como lo señala Adrian McCarthy, itera sobre un contenedor completo).
  3. A diferencia de un bucle for tradicional, for_each te obliga a escribir código que funcionará para cualquier iterador de entrada. Estar restringido de esta manera en realidad puede ser algo bueno porque:

    1. En realidad, es posible que necesite adaptar el código para que funcione para un contenedor diferente más adelante.
    2. Al principio, podría enseñarte algo y / o cambiar tus hábitos para mejor.
    3. Incluso si siempre escribiera para bucles que son perfectamente equivalentes, otras personas que modifiquen el mismo código podrían no hacer esto sin que se les pida usar for_each .
  4. Usar for_each veces hace que sea más obvio que puede usar una función STL más específica para hacer lo mismo. (Como en el ejemplo de Jerry Coffin, no es necesariamente el caso que para cada for_each sea ​​la mejor opción, pero un bucle for no es la única alternativa).


Solía ​​desagradarme std::for_each y pensé que sin lambda, se hizo completamente mal. Sin embargo, cambié de opinión hace un tiempo, y ahora realmente me encanta. Y creo que incluso mejora la legibilidad, y hace que sea más fácil probar tu código de una manera TDD.

El algoritmo std::for_each se puede leer como hacer algo con todos los elementos en el rango , lo que puede mejorar la legibilidad. Supongamos que la acción que desea realizar tiene 20 líneas de largo y la función donde se realiza la acción también tiene 20 líneas de longitud. Eso haría una función de 40 líneas de longitud con un bucle for convencional, y solo alrededor de 20 con std::for_each , por lo tanto, probablemente más fácil de comprender.

Es más probable que los funtores para std::for_each sean más genéricos y, por lo tanto, reutilizables, por ejemplo:

struct DeleteElement { template <typename T> void operator()(const T *ptr) { delete ptr; } };

Y en el código solo tendrías una línea única como std::for_each(v.begin(), v.end(), DeleteElement()) que es ligeramente mejor que un ciclo explícito de IMO.

Todos esos funtores son normalmente más fáciles de obtener bajo pruebas unitarias que un bucle explícito en el medio de una función larga, y eso solo ya es una gran ganancia para mí.

std::for_each también es generalmente más confiable, ya que es menos probable que std::for_each un error con el alcance.

Y, por último, el compilador podría producir un código ligeramente mejor para std::for_each que para ciertos tipos de bucles for-hand-crafted, ya que (for_each) siempre tiene el mismo aspecto para el compilador, y los compiladores pueden poner todo su conocimiento para hacerlo tan bueno como pueden.

Lo mismo se aplica a otros algoritmos find_if como find_if , transform , etc.


es muy subjetivo, algunos dirán que usar for_each hará que el código sea más legible, ya que permite tratar diferentes colecciones con las mismas convenciones. for_each se implementa como un bucle

template<class InputIterator, class Function> Function for_each(InputIterator first, InputIterator last, Function f) { for ( ; first!=last; ++first ) f(*first); return f; }

por lo tanto, depende de usted elegir lo que es correcto para usted.


for_each es más genérico. Puede usarlo para iterar sobre cualquier tipo de contenedor (pasando los iteradores de inicio / final). Puede intercambiar contenedores por debajo de una función que utiliza for_each sin tener que actualizar el código de iteración. Debe tener en cuenta que existen otros contenedores en el mundo que std :: vector y los arrays de C antiguos y simples para ver las ventajas de for_each.

El mayor inconveniente de for_each es que requiere un funtor, por lo que la sintaxis es poco clara. Esto se soluciona en C ++ 0x con la introducción de lambdas:

std::vector<int> container; ... std::for_each(container.begin(), container.end(), [](int& i){ i+= 10; });

Esto no te parecerá extraño en 3 años.


for es un ciclo que puede iterar cada elemento o cada tercio, etc. for_each es para iterar solo cada elemento. Está claro por su nombre. Por lo tanto, es más claro lo que intenta hacer en su código.