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í:
- Ventajas de std :: for_each over for loop 19 respuestas
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.
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
).otra diferencia se refiere a la ruptura del ciclo (con
break
oreturn
en el ciclofor
). Por lo que yo sé, en un ciclofor_each
esto solo puede hacerse lanzando una excepción. Por ejemplofor( 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).
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
yfor_each
, por ejemplomy::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 correctausing
directivas apropiadas. Esto es equivalente ausing std::swap;
cuando se especializaswap()
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 veanstd::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.