c++ - use range based for loop instead




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

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


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

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.


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.


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.


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.




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





coding-style