loop for_each for examples c++ lambda c++11

for_each - foreach c++ examples



para loop vs std:: for_each con lambda (5)

Con respecto al rendimiento, su bucle for llama a std::end repetidamente, mientras que std::for_each no lo hará. Esto puede dar como resultado una diferencia en el rendimiento según el contenedor utilizado.

Esta pregunta ya tiene una respuesta aquí:

Consideremos una función de plantilla escrita en C ++ 11 que itera sobre un contenedor. Excluya la sintaxis de bucle de rango porque todavía no es compatible con el compilador con el que estoy trabajando.

template <typename Container> void DoSomething(const Container& i_container) { // Option #1 for (auto it = std::begin(i_container); it != std::end(i_container); ++it) { // do something with *it } // Option #2 std::for_each(std::begin(i_container), std::end(i_container), [] (typename Container::const_reference element) { // do something with element }); }

¿Cuáles son los pros / cons de for loop vs std::for_each en términos de:

¿una actuación? (No espero ninguna diferencia)

b) legibilidad y mantenibilidad?

Aquí veo muchas desventajas de for_each . No aceptaría una matriz c-style mientras el loop lo haría. La declaración del parámetro formal lambda es muy detallada, no es posible usar auto allí. No es posible salir de for_each .

En preC ++ 11 días, los argumentos en contra eran una necesidad de especificar el tipo para el iterador (no se mantiene) y una posibilidad fácil de escribir mal la condición del ciclo (nunca he cometido tal error en 10 años) .

Como conclusión, mis pensamientos sobre cada for_each contradicen la opinión común. ¿Que me estoy perdiendo aqui?


Creo que hay otras diferencias aún no cubiertas por las respuestas hasta ahora.

  1. un for_each puede aceptar cualquier objeto apropiado invocable, lo que permite "reciclar" el cuerpo del bucle para diferentes bucles for. Por ejemplo (pseudo código)

    for( range_1 ) { lengthy_loop_body } // many lines of code for( range_2 ) { lengthy_loop_body } // the same many lines of code again

    se convierte

    auto loop_body = some_lambda; // many lines of code here only std::for_each( range_1 , loop_body ); // a single line of code std::for_each( range_2 , loop_body ); // another single line of code

    evitando la duplicación y simplificando el mantenimiento del código. (Por supuesto, en una divertida mezcla de estilos también se podría usar un enfoque similar con el bucle for ).

  2. otra diferencia se refiere a la ruptura del ciclo (con break o return en el ciclo for ). Por lo que yo sé, en un ciclo for_each esto solo puede hacerse lanzando una excepción. Por ejemplo

    for( range ) { some code; if(condition_1) return x; // or break more code; if(condition_2) continue; yet more code; }

    se convierte

    try { std::for_each( range , [] (const_reference x) { some code; if(condition_1) throw x; more code; if(condition_2) return; yet more code; } ); } catch(const_reference r) { return r; }

    con los mismos efectos con respecto a la invocación de destructores para objetos con alcance del cuerpo del bucle y el cuerpo de la función (alrededor del bucle).

  3. El principal beneficio de for_each es, en mi humilde opinión, que uno puede sobrecargarlo para ciertos tipos de contenedores, cuando la iteración simple no es tan eficiente. Por ejemplo, considere un contenedor que contiene una lista vinculada de bloques de datos, cada bloque contiene una matriz contigua de elementos, similar a (omitiendo el código irrelevante)

    namespace my { template<typename data_type, unsigned block_size> struct Container { struct block { const block*NEXT; data_type DATA[block_size]; block() : NEXT(0) {} } *HEAD; }; }

    entonces un iterador directo apropiado para este tipo requeriría verificar el final del bloque en cada incremento y el operador de comparación necesita comparar tanto el puntero del bloque como el índice dentro de cada bloque (omitiendo el código irrelevante):

    namespace my { template<typename data_type, unsigned block_size> struct Container { struct iterator { const block*B; unsigned I; iterator() = default; iterator&operator=(iterator const&) = default; iterator(const block*b, unsigned i) : B(b), I(i) {} iterator& operator++() { if(++I==block_size) { B=B->NEXT; I=0; } // one comparison and branch return*this; } bool operator==(const iterator&i) const { return B==i.B && I==i.I; } // one or two comparisons bool operator!=(const iterator&i) const { return B!=i.B || I!=i.I; } // one or two comparisons const data_type& operator*() const { return B->DATA[I]; } }; iterator begin() const { return iterator(HEAD,0); } iterator end() const { return iterator(0,0); } }; }

    este tipo de iterador funciona correctamente con for y for_each , por ejemplo

    my::Container<int,5> C; for(auto i=C.begin(); i!=C.end(); // one or two comparisons here ++i) // one comparison here and a branch f(*i);

    pero requiere dos o tres comparaciones por iteración, así como una rama. Una forma más eficiente es sobrecargar la función for_each() para realizar un bucle en el puntero y el índice de bloques por separado:

    namespace my { template<typename data_type, int block_size, typename FuncOfDataType> FuncOfDataType&& for_each(typename my::Container<data_type,block_size>::iterator i, typename my::Container<data_type,block_size>::iterator const&e, FuncOfDataType f) { for(; i.B != e.B; i.B++,i.I=0) for(; i.I != block_size; i.I++) f(*i); for(; i.I != e.I; i.I++) f(*i); return std::move(f); } } using my::for_each; // ensures that the appropriate using std::for_each; // version of for_each() is used

    que requiere solo una comparación para la mayoría de las iteraciones y no tiene ramas (tenga en cuenta que las ramas pueden tener un impacto desagradable en el rendimiento). Tenga en cuenta que no necesitamos definir esto en el espacio de nombres std (que podría ser ilegal), pero podemos asegurarnos de que se utiliza la versión correcta using directivas apropiadas. Esto es equivalente a using std::swap; cuando se especializa swap() para ciertos tipos definidos por el usuario.


En efecto; en el caso de utilizar una expresión Lambda, debe declarar el tipo y nombre del parámetro, de modo que no se gana nada.

Pero será increíble tan pronto como quiera llamar a una función (nombrada) u objeto-función con esto. (Recuerde que puede combinar cosas similares a las funciones a través de std::bind ).

Los libros de Scott Meyers (creo que fue eficaz STL ) describen estos estilos de programación muy buenos y claros.


Primero, no puedo ver mucha diferencia entre estos dos, porque for_each se implementa usando for loop. Pero tenga en cuenta que for_each es una función que tiene un valor de retorno.

En segundo lugar, utilizaré la sintaxis de bucle de rango una vez disponible en este caso, ya que este día llegaría pronto de todos modos.


  • La versión std::for_each visitará cada elemento exactamente una vez. Alguien que lea el código puede saberlo tan pronto como vean std::for_each , ya que no hay nada que se pueda hacer en el lambda para meterse con el iterador. En el bucle for tradicional, debe estudiar el cuerpo del bucle para obtener un flujo de control inusual ( continue , break , return ) y dinking con el iterador (por ejemplo, en este caso, omita el siguiente elemento con ++it ).

  • Puedes cambiar trivialmente el algoritmo en la solución lambda. Por ejemplo, podría hacer un algoritmo que visita cada enésimo elemento. En muchos casos, realmente no quería un bucle for de todos modos, sino un algoritmo diferente como copy_if . Usar un algoritmo + lambda, a menudo es más fácil de cambiar y es un poco más conciso.

  • Por otro lado, los programadores están mucho más acostumbrados a los bucles for tradicionales, por lo que pueden encontrar que el algoritmo + lambda es más difícil de leer.