c++ instead Ventajas de std:: for_each over for loop




use range based for loop instead (16)

¿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?


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.


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.


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)));

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.


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); }

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.


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.



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.


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).


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?


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.


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;
      }
   }
}

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)


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.





coding-style