¿Hay alguna ventaja de std::for_each
over for
loop? Para mí, std::for_each
solo parece obstaculizar la legibilidad del código. ¿Por qué algunos estándares de codificación recomiendan su uso?
¿Hay alguna ventaja de std::for_each
over for
loop? Para mí, std::for_each
solo parece obstaculizar la legibilidad del código. ¿Por qué algunos estándares de codificación recomiendan su uso?
Respuestas:
Lo bueno de C ++ 11 (anteriormente llamado C ++ 0x) es que este debate agotador se resolverá.
Quiero decir, nadie en su sano juicio, que quiera iterar sobre una colección completa, seguirá usando 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 en rangofor
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 muchos más foreach
bucles que for
bucles clásicos en cada código Java o C # reciente que vi.
Element & e
cuando auto & e
(o auto const &e
) se vea mejor. Usaría Element const e
(sin referencia) cuando quiero una conversión implícita, digamos cuando la fuente es una colección de diferentes tipos, y quiero que se conviertan Element
.
Aquí hay algunas razones:
Parece dificultar la legibilidad solo porque no estás acostumbrado y / o no estás usando las herramientas adecuadas para hacerlo realmente fácil. (vea boost :: range y boost :: bind / boost :: lambda para ayudantes. Muchos de estos irán a C ++ 0x y harán que for_each y las funciones relacionadas sean más útiles).
Le permite escribir un algoritmo encima de for_each que funciona con cualquier iterador.
Reduce la posibilidad de estúpidos errores de tipeo.
También abre su mente para el resto de los algoritmos de STL, como find_if
, sort
, replace
, etc, y estos no se verá tan extraño más. Esto puede ser una gran victoria.
Actualización 1:
Lo que es más importante, te ayuda a ir más allá for_each
de los bucles for-for como si fuera todo lo que hay, y mira los otros STL-alogs, como find / sort / division / copy_replace_if, ejecución paralela ... o lo que sea.
Se puede escribir una gran cantidad de procesamiento de manera muy concisa usando "el resto" de los hermanos de for_each, pero si todo lo que hace es escribir un bucle for con varias lógicas internas, nunca aprenderá cómo usarlos, y terminar inventando la rueda una y otra vez.
Y (el estilo de rango disponible próximamente 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 = bananas.begin(); i != bananas.end(); ++i) {
my_monkey->eat(*i);
}
Especialmente si tienes varias funciones para llamar en orden ... pero tal vez solo soy 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 también estará allí en C ++ 0x?
outer_class::inner_class::iterator
o son argumentos de plantilla: typename std::vector<T>::iterator
... el for for sí mismo puede encontrarse con una construcción de muchas líneas en sí mismo
for_each
en el segundo ejemplo es incorrecto (debería serfor_each( bananas.begin(), bananas.end(),...
for_each
Es más genérico. Puede usarlo para iterar sobre cualquier tipo de contenedor (pasando los iteradores de inicio / fin). Potencialmente, puede intercambiar contenedores 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 no sean std::vector
y matrices de C antiguas para ver las ventajas de for_each
.
El principal inconveniente de for_each
es que se necesita un functor, por lo que la sintaxis es torpe. Esto se soluciona en C ++ 11 (anteriormente 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 ( int v : int_vector ) {
(incluso si se puede simular hoy con BOOST_FOREACH)
std::for_each(container, [](int& i){ ... });
. Quiero decir, ¿por qué uno se ve obligado a escribir el contenedor dos veces?
container.each { ... }
sin mencionar los iteradores de inicio y fin. Me parece un poco redundante que tenga que especificar el iterador final todo el tiempo.
Personalmente, cada vez que necesito salir de mi camino para usar std::for_each
(escribir functores de propósito especial / boost::lambda
s complicados ), encuentro BOOST_FOREACH
y C ++ 0x basado en el rango para 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)));
Es muy subjetivo, algunos dirán que el uso for_each
hará que el código sea más legible, ya que permite tratar diferentes colecciones con las mismas convenciones.
for_each
itslef 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;
}
así que depende de usted elegir lo que es adecuado para usted.
Como muchas de las funciones del algoritmo, una reacción inicial es pensar que es más ilegible usar foreach que un bucle. Ha sido un tema de muchas guerras de llamas.
Una vez que te acostumbres al idioma, te puede resultar útil. Una ventaja obvia es que obliga al codificador a separar el contenido interno del bucle de la funcionalidad de iteración real. (OK, creo que es una ventaja. Otros dicen que solo estás cortando el código sin ningún beneficio real).
Otra ventaja es que cuando veo foreach, sé que cada elemento será procesado o se lanzará una excepción.
Un bucle for permite varias opciones para terminar el bucle. Puede dejar que el ciclo siga su curso completo, o puede usar la palabra clave break para saltar explícitamente del ciclo, o usar la palabra clave return para salir de toda la función mid-loop. Por el contrario, 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 ciclo 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;
}
}
}
std::for_each()
en el estándar anterior (el de la época de esta publicación), debe usar un functor con nombre, que fomenta la legibilidad como dice y prohíbe salir del bucle prematuramente. Pero entonces el for
bucle equivalente no tiene más que una llamada a función, y eso también prohíbe la ruptura prematura. Pero aparte de eso, creo que hiciste un excelente comentario al decir que se std::for_each()
impone pasar por todo el rango.
En su mayoría tiene razón: la mayoría de las veces, std::for_each
es una pérdida neta. Me gustaría ir tan lejos como para comparar for_each
a goto
. goto
proporciona el control de flujo más versátil posible: puede usarlo para implementar prácticamente cualquier otra estructura de control que pueda imaginar. Sin embargo, esa misma versatilidad significa que ver un goto
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 lo usa goto
excepto como último recurso.
Entre los algoritmos estándar, for_each
es muy similar: se puede usar para implementar prácticamente cualquier cosa, lo que significa que ver no for_each
le 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
(digamos) 1970 más o menos: algunas personas se dieron cuenta del hecho de que debería usarse solo como último recurso, pero muchos todavía lo consideran el algoritmo principal, y rara vez, si alguna vez, usa alguna otra. La gran mayoría de las veces, incluso una rápida mirada revelaría que una de las alternativas era drásticamente superior.
Solo por ejemplo, estoy bastante seguro de que he perdido la cuenta de cuántas veces he visto a personas escribir código para imprimir el contenido 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 puesto está preguntando por qué combinación de bind1st
, mem_fun
, etc. Tienen que 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 lo hace, y para cuando lo hayas hecho funcionar, es difícil encontrar esos pocos bits de código relacionados con lo que hay pasando entre las piezas que lo mantienen unido.
Afortunadamente, hay una manera mucho mejor. Agregue una sobrecarga normal del insertador de flujo para XXX:
std::ostream &operator<<(std::ostream *os, XXX const &x) {
return x.print(os);
}
y uso std::copy
:
std::copy(coll.begin(), coll.end(), std::ostream_iterator<XXX>(std::cout, "\n"));
Que hace el trabajo - y toma prácticamente ningún trabajo en absoluto a darse cuenta de que se imprime el contenido de coll
a std::cout
.
boost::mem_fn(&XXX::print)
lugar deXXX::print
std::cout
como argumento para que funcione).
La ventaja de la escritura funcional para ser más legible, podría no aparecer cuando for(...)
y 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, generalice los bucles for a funciones de una línea =)
Fácil: for_each
es útil cuando ya tiene una función para manejar cada elemento de la matriz, por lo que no tiene que escribir una 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 for
bucle a distancia solo itera sobre contenedores completos de principio a fin, mientras que for_each
es más flexible.
El for_each
bucle está destinado a ocultar los iteradores (detalle de cómo se implementa un bucle) 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. Eso se convierte en un código menos legible ya que los objetos de functor no pueden definirse en el lugar (las clases locales definidas dentro de una función no pueden usarse como argumentos de plantilla) y la implementación del bucle debe alejarse 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 simplificarlo:
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 una función / método para llamar. La implementación podría proporcionar otras implementaciones del for_each
bucle (piense en el procesamiento paralelo).
El próximo estándar se ocupa de algunas de las deficiencias de diferentes maneras, permitirá clases definidas localmente como 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 allí. De hecho, ni siquiera necesita usar la sintaxis de clase para definir el functor, pero use una lambda allí:
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
}
Tiendo a mezclar la for_each
construcción 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 for_each
construcción que le quita al código muchas cosas de iterador de placa de caldera. Cuando necesito algo más complejo y no puedo implementar un functor solo un par de líneas por encima del uso real, enrollo 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 eso)
Además de la legibilidad y el rendimiento, un aspecto comúnmente ignorado es la consistencia. Hay muchas formas de implementar un ciclo 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 intermedios en diferentes niveles de eficiencia y potencial de error.
Sin embargo, el uso de for_each impone la coherencia al abstraer el ciclo:
for_each(c.begin(), c.end(), do_something);
Lo único de lo que debe preocuparse ahora es: ¿implementa el cuerpo del bucle como función, functor o lambda utilizando las funciones Boost o C ++ 0x? Personalmente, prefiero preocuparme por eso que por cómo implementar o leer un bucle aleatorio para / mientras.
No me gustaba std::for_each
y pensaba que sin lambda, estaba completamente equivocado. Sin embargo, cambié de opinión hace algún tiempo, y ahora realmente me encanta. Y creo que incluso mejora la legibilidad y hace que sea más fácil probar su código de forma TDD.
El std::for_each
algoritmo se puede leer como hacer algo con todos los elementos dentro del rango , lo que puede mejorar la legibilidad. Digamos que la acción que desea realizar es de 20 líneas de largo, y la función donde se realiza la acción también tiene aproximadamente 20 líneas de largo. Eso haría una función de 40 líneas de largo con un bucle convencional, y solo alrededor de 20 con std::for_each
, por lo tanto, probablemente sea más fácil de comprender.
Los functores para std::for_each
son más propensos a ser 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 como std::for_each(v.begin(), v.end(), DeleteElement())
IMO ligeramente mejor que un bucle explícito.
Todos esos functores son normalmente más fáciles de obtener bajo pruebas unitarias que un bucle for explícito en medio de una función larga, y eso solo ya es una gran victoria para mí.
std::for_each
También es generalmente más confiable, ya que es menos probable que cometas un error con el rango.
Y, por último, el compilador puede producir un código ligeramente mejor std::for_each
que para ciertos tipos de bucles for hechos a mano, ya que (para cada uno) siempre se ve igual para el compilador, y los escritores del compilador pueden poner todo su conocimiento, para que sea tan bueno como ellos lata.
Lo mismo se aplica a otros algoritmos estándar como find_if
, transform
etc.
for
es para 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 tiene la intención de hacer en su código.
++
. Inusual tal vez, pero también lo es un bucle for.
transform
para no confundir a alguien.
Si utiliza con frecuencia otros algoritmos de STL, existen varias ventajas para for_each
:
A diferencia de un bucle for tradicional, for_each
te obliga a escribir código que funcione para cualquier iterador de entrada. Estar restringido de esta manera puede ser algo bueno porque:
for_each
.El uso a 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 for_each
sea la mejor opción, pero un bucle for no es la única alternativa).
Con C ++ 11 y dos plantillas simples, puedes escribir
for ( auto x: range(v1+4,v1+6) ) {
x*=2;
cout<< x <<' ';
}
como reemplazo for_each
o bucle. Por qué elegirlo se reduce a brevedad y seguridad, no hay posibilidad de error en una expresión que no está allí.
Para mí, for_each
siempre fue mejor por el mismo motivo cuando el cuerpo del bucle ya es un ficticio, y aprovecharé cualquier ventaja que pueda obtener.
Todavía usa la expresión de tres for
, pero ahora cuando ve una, sabe que hay algo que entender allí, no es repetitivo. Yo odio repetitivo. Me molesta su existencia. No es un código real, no hay nada que aprender al leerlo, es solo una cosa más que debe verificarse. El esfuerzo mental se puede medir por lo fácil que es oxidarse al comprobarlo.
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); }
En su mayoría, tendrá que recorrer toda la colección . Por lo tanto, le sugiero que escriba su propia variante for_each (), tomando solo 2 parámetros. Esto le permitirá reescribir el ejemplo de Terry Mahaffey como:
for_each(container, [](int& i) {
i += 10;
});
Creo que esto es más legible que un bucle for. Sin embargo, esto requiere las extensiones del compilador C ++ 0x.
Encuentro que for_each es malo para la legibilidad. El concepto es bueno, pero c ++ hace que sea muy difícil escribir legible, al menos para mí. Las expresiones c ++ 0x lamda ayudarán. Me gusta mucho 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 acostumbre. Tal vez en 5 años me haya acostumbrado y no lo piense más, 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 el bucle más claro para leer y la explicidad utilizando variables con nombre 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.
Puede hacer que el iterador sea una llamada a una función que se realiza en cada iteración a través del bucle.
Ver aquí: http://www.cplusplus.com/reference/algorithm/for_each/
for_each
hace, en cuyo caso, no responde la pregunta sobre sus ventajas.
Porque el bucle puede romperse; No quiero ser un loro para Herb Sutter, así que aquí está el enlace a su presentación: http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-835T Asegúrese de leer los comentarios también :)
for_each
permítanos implementar el patrón Fork-Join . Aparte de eso, admite una interfaz fluida .
Podemos agregar implementación gpu::for_each
para usar cuda / gpu para computación heterogénea-paralela llamando a la tarea lambda en múltiples trabajadores.
gpu::for_each(users.begin(),users.end(),update_summary);
// all summary is complete now
// go access the user-summary here.
Y gpu::for_each
puede esperar a que los trabajadores trabajen en todas las tareas lambda para finalizar antes de ejecutar las siguientes declaraciones.
Nos permite escribir código legible por humanos de manera concisa.
accounts::erase(std::remove_if(accounts.begin(),accounts.end(),used_this_year));
std::for_each(accounts.begin(),accounts.end(),mark_dormant);
std::for_each
cuando se usa conboost.lambda
o aboost.bind
menudo puede mejorar la legibilidad