Avantages de std :: for_each over for loop

Existe-t-il des avantages de std::for_each over for loop? Pour moi, std::for_each semble seulement empêcher la lisibilité du code. Pourquoi alors certaines normes de codage recommandent-elles son utilisation?

La bonne chose avec C ++ 11 (précédemment appelé C ++ 0x) est que ce débat fastidieux sera réglé.

Je veux dire, personne dans leur bon esprit, qui veut parcourir toute une collection, utilisera toujours cette

 for(auto it = collection.begin(); it != collection.end() ; ++it) { foo(*it); } 

Ou ca

 for_each(collection.begin(), collection.end(), [](Element& e) { foo(e); }); 

lorsque la syntaxe de boucle basée for la plage est disponible:

 for(Element& e : collection) { foo(e); } 

Ce type de syntaxe est disponible depuis un certain temps en Java et en C #, et en fait, il y a beaucoup plus de boucles foreach que de boucles classiques dans chaque code Java ou C # récent.

Voici quelques raisons:

  1. Cela semble nuire à la lisibilité simplement parce que vous n’y êtes pas habitué et / ou que vous n’utilisez pas les bons outils pour le rendre vraiment facile. (Voir boost :: range et boost :: bind / boost :: lambda pour les helpers. Beaucoup d’entre eux iront dans C ++ 0x et rendront les fonctions for_each et connexes plus utiles.)

  2. Il vous permet d’écrire un algorithme au-dessus de for_each qui fonctionne avec n’importe quel iterator.

  3. Cela réduit le risque de bugs de frappe stupides.

  4. Cela ouvre également votre esprit aux autres algorithmes STL, comme find_if , sort , etc, et ils ne seront plus aussi étranges. Cela peut être une énorme victoire.

Mise à jour 1:

Plus important encore, cela vous aide à aller au-delà de for_each vs for-loops, comme c’est tout ce qu’il y a, et à regarder les autres alarmes STL, comme find / sort / partition / copy_replace_if, l’exécution parallèle … ou autre.

Beaucoup de traitement peut être écrit de manière très concise en utilisant “le rest” des frères et sœurs de for_each, mais si vous ne faites qu’écrire une boucle avec diverses logiques internes, vous n’apprendrez jamais à les utiliser, et vous finissent par inventer la roue encore et encore.

Et (le style de gamme bientôt disponible pour_each):

 for_each(monsters, boost::mem_fn(&Monster::think)); 

Ou avec des lambdas C ++ x11:

 for_each(monsters, [](Monster& m) { m.think(); }); 

est IMO plus lisible que:

 for(Monsters::iterator i = monsters.begin(); i != monsters.end(); ++i) { i->think(); } 

Aussi ceci (ou avec des lambdas, voir d’autres):

 for_each(bananas, boost::bind(&Monkey::eat, my_monkey, _1)); 

Est plus concis que:

 for(Bananas::iterator i = banans.begin(); i != bananas.end(); ++i) { my_monkey->eat(*i); } 

Surtout si vous avez plusieurs fonctions à appeler dans l’ordre … mais c’est peut-être juste moi. 😉

Mise à jour 2 : J’ai écrit mes propres enveloppes à une page de stl-algos qui fonctionnent avec des plages au lieu de deux iterators. boost :: range_ex, une fois sorti, inclura cela et peut-être qu’il sera là aussi en C ++ 0x?

for_each est plus générique. Vous pouvez l’utiliser pour effectuer une itération sur n’importe quel type de conteneur (en transmettant les iterators début / fin). Vous pouvez éventuellement échanger des conteneurs sous une fonction qui utilise for_each sans avoir à mettre à jour le code d’itération. Vous devez considérer qu’il existe d’autres conteneurs dans le monde que std :: vector et les anciens tableaux C pour voir les avantages de for_each.

L’inconvénient majeur de for_each est qu’il prend un foncteur, donc la syntaxe est maladroite. Ceci est corrigé dans C ++ 0x avec l’introduction de lambdas:

 std::vector container; ... std::for_each(container.begin(), container.end(), [](int& i){ i+= 10; }); 

Cela ne vous semblera pas étrange dans 3 ans.

Personnellement, chaque fois que je devrais utiliser std::for_each (écrire des foncteurs spéciaux / boost::lambda compliqué boost::lambda s), je trouve que BOOST_FOREACH et C ++ 0x sont basés sur la plage pour plus de clarté:

 BOOST_FOREACH(Monster* m, monsters) { if (m->has_plan()) m->act(); } 

contre

 std::for_each(monsters.begin(), monsters.end(), if_then(bind(&Monster::has_plan, _1), bind(&Monster::act, _1))); 

C’est très subjectif, certains diront que l’utilisation de for_each rendra le code plus lisible, car il permet de traiter différentes collections avec les mêmes conventions. for_each itslef est implémenté en boucle

 template Function for_each(InputIterator first, InputIterator last, Function f) { for ( ; first!=last; ++first ) f(*first); return f; } 

alors à vous de choisir ce qui est bon pour vous.

Comme la plupart des fonctions de l’algorithme, une première réaction consiste à penser qu’il est plus illisible d’utiliser foreach qu’une boucle. Cela a été le sujet de nombreuses guerres de flammes.

Une fois que vous vous êtes habitué à l’idiome, vous pouvez le trouver utile. Un avantage évident est que cela oblige le codeur à séparer le contenu interne de la boucle de la fonctionnalité d’itération réelle. (OK, je pense que c’est un avantage. D’autres disent que vous ne faites que couper le code sans véritable avantage).

Un autre avantage est que lorsque je vois foreach, je sais que chaque élément sera traité ou qu’une exception sera lancée.

Une boucle for permet plusieurs options pour terminer la boucle. Vous pouvez laisser la boucle s’exécuter complètement, ou vous pouvez utiliser le mot-clé break pour sortir explicitement de la boucle, ou utiliser le mot-clé return pour quitter l’intégralité de la fonction. En revanche, foreach n’autorise pas ces options, ce qui le rend plus lisible. Vous pouvez simplement jeter un coup d’œil sur le nom de la fonction et connaître la nature complète de l’itération.

Voici un exemple de confusion pour la boucle:

 for(std::vector::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::iterator ip = widget.GetParts().begin(); ip != widget.GetParts().end(); ++ip) { if(ip->IsBroken()) { return false; } } } 

Vous avez plutôt raison: la plupart du temps, std::for_each est une perte nette. J’irais jusqu’à comparer for_each à goto . goto fournit le contrôle de stream le plus polyvalent possible – vous pouvez l’utiliser pour mettre en œuvre pratiquement toute autre structure de contrôle que vous pouvez imaginer. Cette très grande polyvalence signifie cependant que le fait de voir un goto de façon isolée ne vous dit pratiquement rien sur ce que l’on entend faire dans cette situation. En conséquence, presque personne dans leur bon esprit n’utilise goto sauf en dernier recours.

Parmi les algorithmes standard, for_each est très similaire – il peut être utilisé pour implémenter pratiquement n’importe quoi, ce qui signifie que voir for_each vous dit pratiquement rien sur ce pour quoi il est utilisé dans cette situation. Malheureusement, l’attitude des gens vis-à-vis de for_each concerne leur attitude vis-à-vis de goto en 1970, par exemple – quelques personnes ont compris qu’elle ne devait être utilisée qu’en dernier recours, mais beaucoup la considèrent encore comme l’algorithme primaire. , et rarement si jamais utiliser un autre. La grande majorité du temps, même un coup d’œil rapide révélerait que l’une des alternatives était radicalement supérieure.

Par exemple, je suis à peu près certain que j’ai perdu de vue le nombre de fois où des personnes ont écrit du code pour imprimer le contenu d’une collection à l’aide de for_each . Basé sur les messages que j’ai vus, cela pourrait bien être l’utilisation la plus courante de for_each . Ils finissent avec quelque chose comme:

 class XXX { // ... public: std::ostream &print(std::ostream &os) { return os << "my data\n"; } }; 

Et leur message demande quelle combinaison de bind1st , mem_fun , etc. ils ont besoin pour créer quelque chose comme:

 std::vector coll; std::for_each(coll.begin(), coll.end(), XXX::print); 

travailler, et imprimer les éléments de coll . Si cela fonctionnait vraiment exactement comme je l’ai écrit ici, ce serait médiocre, mais ce n’est pas le cas - et au moment où cela fonctionne, il est difficile de trouver ces quelques morceaux de code liés à passe entre les pièces qui le tiennent ensemble.

Heureusement, il y a une bien meilleure façon. Ajoutez une surcharge d'insertion de stream normale pour XXX:

 std::ostream &operator<<(std::ostream *os, XXX const &x) { return x.print(os); } 

et utilisez std::copy :

 std::copy(coll.begin(), coll.end(), std::ostream_iterator(std::cout, "\n")); 

Cela fonctionne - et ne prend pratiquement aucun travail pour comprendre qu'il imprime le contenu de coll à std::cout .

L’avantage d’écrire fonctionnel pour être plus lisible, peut ne pas apparaître quand for(...) et for_each(... ).

Si vous utilisez tous les algorithmes dans functional.h, au lieu d’utiliser des boucles for, le code devient beaucoup plus lisible;

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

est beaucoup plus lisible 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); } 

Et c’est ce que j’estime si gentil, généraliser les for-loops à une ligne fonctions =)

Facile: for_each est utile lorsque vous avez déjà une fonction pour gérer chaque élément du tableau, vous n’avez donc pas besoin d’écrire un lambda. Certainement, cela

 for_each(a.begin(), a.end(), a_item_handler); 

est mieux que

 for(auto& item: a) { a_item_handler(a); } 

Aussi, rangeed for loop ne parcourt que des conteneurs entiers du début à la fin, tandis que for_each est plus flexible.

La boucle for_each est destinée à masquer les iterators (détail de la mise en œuvre d’une boucle) à partir du code utilisateur et à définir une sémantique claire sur l’opération: chaque élément sera répété exactement une fois.

Le problème avec la lisibilité de la norme actuelle est qu’il nécessite un foncteur comme dernier argument au lieu d’un bloc de code. Dans de nombreux cas, vous devez donc écrire un type de foncteur spécifique. Cela se transforme en code moins lisible car les objects foncteurs ne peuvent pas être définis sur place (les classes locales définies dans une fonction ne peuvent pas être utilisées comme arguments de modèle) et l’implémentation de la boucle doit être éloignée de la boucle réelle.

 struct myfunctor { void operator()( int arg1 ) { code } }; void apply( std::vector const & v ) { // code std::for_each( v.begin(), v.end(), myfunctor() ); // more code } 

Notez que si vous souhaitez effectuer une opération spécifique sur chaque object, vous pouvez utiliser std::mem_fn ou boost::bind ( std::bind dans le prochain standard) ou boost::lambda (lambdas dans le prochain standard) pour simplifier:

 void function( int value ); void apply( std::vector const & v ) { // code std::for_each( v.begin(), v.end(), boost::bind( function, _1 ) ); // code } 

Ce qui n’est pas moins lisible et plus compact que la version roulée à la main si vous avez la fonction / méthode d’appeler en place. L’implémentation pourrait fournir d’autres implémentations de la boucle for_each (pensez au parallel processing).

La prochaine norme prend en charge certaines des lacunes de différentes manières, elle permettra aux classes définies localement comme des arguments pour les modèles:

 void apply( std::vector const & v ) { // code struct myfunctor { void operator()( int ) { code } }; std::for_each( v.begin(), v.end(), myfunctor() ); // code } 

Améliorer la localité du code: lorsque vous naviguez, vous voyez ce qu’il fait là. En fait, vous n’avez même pas besoin d’utiliser la syntaxe de classe pour définir le foncteur, mais utilisez un lambda directement:

 void apply( std::vector const & v ) { // code std::for_each( v.begin(), v.end(), []( int ) { // code } ); // code } 

Même si pour for_each il y aura une construction spécifique qui la rendra plus naturelle:

 void apply( std::vector const & v ) { // code for ( int i : v ) { // code } // code } 

J’ai tendance à mélanger la construction for_each avec des boucles roulées à la main. Lorsque seul un appel à une fonction ou à une méthode existante est ce dont j’ai besoin ( for_each( v.begin(), v.end(), boost::bind( &Type::update, _1 ) ) ) je recherche la construction for_each enlève du code beaucoup de choses d’iterator de plaque de chaudière. Quand j’ai besoin de quelque chose de plus complexe et que je ne peux pas implémenter un foncteur juste quelques lignes au-dessus de l’utilisation réelle, je roule ma propre boucle (maintient l’opération en place). Dans les sections non critiques du code, je pourrais aller avec BOOST_FOREACH (un collègue m’a mis dedans)

Outre la lisibilité et la performance, l’un des aspects souvent négligés est la cohérence. Il existe de nombreuses manières d’implémenter un iterator de boucle for (or while), à ​​partir de:

 for (C::iterator iter = c.begin(); iter != c.end(); iter++) { do_something(*iter); } 

à:

 C::iterator iter = c.begin(); C::iterator end = c.end(); while (iter != end) { do_something(*iter); ++iter; } 

avec de nombreux exemples intermédiaires à différents niveaux d’efficacité et de potentiel de bogue.

L’utilisation de for_each, cependant, renforce la cohérence en supprimant la boucle:

 for_each(c.begin(), c.end(), do_something); 

La seule chose dont vous devez vous soucier maintenant est la suivante: implémentez-vous le corps de la boucle en tant que fonction, foncteur ou lambda en utilisant les fonctionnalités Boost ou C ++ 0x? Personnellement, je préfèrerais m’inquiéter de la manière de mettre en œuvre ou de lire une boucle aléatoire pour / while.

for est pour la boucle qui peut itérer chaque élément ou chaque tiers, etc. for_each sert uniquement à itérer chaque élément. C’est clair de son nom. Il est donc plus clair de savoir ce que vous avez l’intention de faire dans votre code.

Je std::for_each pas std::for_each et pensais que sans lambda, c’était complètement faux. Cependant, j’ai changé d’avis il y a quelque temps, et maintenant je l’aime vraiment. Et je pense que cela améliore même la lisibilité et facilite le test de votre code de manière TDD.

L’algorithme std::for_each peut être lu comme quelque chose avec tous les éléments de la plage , ce qui peut améliorer la lisibilité. Supposons que l’action que vous souhaitez effectuer comporte 20 lignes, et que la fonction dans laquelle l’action est effectuée comporte également environ 20 lignes. Cela ferait une fonction de 40 lignes avec une boucle conventionnelle, et seulement environ 20 avec std::for_each , donc plus facile à comprendre.

Les foncteurs de std::for_each sont plus susceptibles d’être plus génériques et donc réutilisables, par exemple:

 struct DeleteElement { template  void operator()(const T *ptr) { delete ptr; } }; 

Et dans le code, vous n’auriez qu’une ligne std::for_each(v.begin(), v.end(), DeleteElement()) comme std::for_each(v.begin(), v.end(), DeleteElement()) qui est légèrement meilleure qu’une boucle explicite.

Tous ces foncteurs sont normalement plus faciles à obtenir sous des tests unitaires qu’une boucle explicite au milieu d’une longue fonction, et cela seul est déjà une grande victoire pour moi.

std::for_each est également généralement plus fiable, car vous êtes moins susceptible de commettre une erreur avec range.

Et enfin, le compilateur pourrait produire un code légèrement meilleur pour std::for_each que pour certains types de boucles for hand, car il (for_each) a toujours la même apparence pour le compilateur, et les auteurs de compilateurs peuvent mettre toutes leurs connaissances pour le rendre aussi bien qu’ils peuvent.

La même chose s’applique aux autres algorithmes std comme find_if , transform etc.

Avec C ++ 11 et deux modèles simples, vous pouvez écrire

  for ( auto x: range(v1+4,v1+6) ) { x*=2; cout<< x <<' '; } 

en remplacement de for_each ou d'une boucle. Pourquoi choisir cela se résume à la brièveté et à la sécurité, il n'y a aucune chance d'erreur dans une expression qui n'est pas là.

Pour moi, for_each a toujours été meilleur sur le même terrain quand le corps de la boucle est déjà un foncteur, et je prendrai tout avantage possible.

Vous utilisez toujours les trois expressions, mais maintenant, quand vous en voyez une, vous savez qu'il y a quelque chose à comprendre, ce n'est pas un passe-partout. Je déteste le passe- partout. Je lui en veux. Ce n'est pas du vrai code, il n'y a rien à apprendre en le lisant, c'est juste une autre chose à vérifier. L'effort mental peut être mesuré par la facilité avec laquelle il se rouille pour le vérifier.

Les modèles sont

 template 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 range_ range(iter const &begin, iter const &end) { return range_(begin,end); } 

Je trouve que for_each est mauvais pour la lisibilité. Le concept est bon mais c ++ rend très difficile l’écriture, du moins pour moi. Les expressions c ++ 0x lamda vous aideront. J’aime beaucoup l’idée des lamdas. Cependant, à première vue, je pense que la syntaxe est très moche et je ne suis pas sûr à 100% que je vais m’y habituer. Peut-être que dans 5 ans je m’y serai habitué et que je n’y réfléchirai plus, mais peut-être pas. Le temps nous le dira 🙂

Je préfère utiliser

 vector::iterator istart = container.begin(); vector::iterator iend = container.end(); for(vector::iterator i = istart; i != iend; ++i) { // Do stuff } 

Je trouve que la lecture explicite d’une boucle plus claire et l’utilisation explicite de variables nommées pour les iterators de début et de fin réduisent le fouillis dans la boucle for.

Bien sûr, les cas varient, c’est ce que je trouve habituellement le mieux.

Si vous utilisez fréquemment d’autres algorithmes de la STL, for_each présente plusieurs avantages:

  1. Ce sera souvent plus simple et moins sujet aux erreurs qu’une boucle for, en partie parce que vous serez habitué aux fonctions avec cette interface, et en partie parce que c’est en fait un peu plus concis dans de nombreux cas.
  2. Bien qu’une boucle basée sur la scope puisse être encore plus simple, elle est moins flexible (comme l’a noté Adrian McCarthy, elle parcourt tout un conteneur).
  3. Contrairement à une boucle for traditionnelle, for_each vous oblige à écrire du code qui fonctionnera pour tout iterator en entrée. Être restreint de cette manière peut être une bonne chose car:

    1. Vous pourriez en fait avoir besoin d’adapter le code pour travailler ultérieurement sur un autre conteneur.
    2. Au début, cela pourrait vous apprendre quelque chose et / ou changer vos habitudes pour le mieux.
    3. Même si vous écrivez toujours pour des boucles parfaitement équivalentes, les autres personnes qui modifient le même code peuvent ne pas le faire sans être invité à utiliser for_each .
  4. L’utilisation de for_each rend parfois plus évident l’utilisation d’une fonction STL plus spécifique pour faire la même chose. (Comme dans l’exemple de Jerry Coffin, ce n’est pas nécessairement le cas, mais for_each est la meilleure option, mais une boucle for n’est pas la seule alternative.)

Vous pouvez faire en sorte que l’iterator soit un appel à une fonction exécutée à chaque itération dans la boucle.

Voir ici: http://www.cplusplus.com/reference/algorithm/for_each/

La plupart du temps, vous devrez parcourir toute la collection . Par conséquent, je vous suggère d’écrire votre propre variante for_each () en prenant seulement 2 parameters. Cela vous permettra de réécrire l’exemple de Terry Mahaffey comme suit :

 for_each(container, [](int& i) { i += 10; }); 

Je pense que cela est en effet plus lisible qu’une boucle for. Toutefois, cela nécessite les extensions du compilateur C ++ 0x.

Car la boucle peut casser; Je ne veux pas être un perroquet pour Herb Sutter alors voici le lien vers sa présentation: http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-835T Lisez bien les commentaires aussi 🙂

for_each nous permet d’implémenter le modèle Fork-Join . À part cela, il prend en charge l’ interface fluide .

modèle de jointure

Nous pouvons append la mise en œuvre gpu::for_each pour utiliser cuda / gpu pour le calcul en parallèle hétérogène en appelant la tâche lambda sur plusieurs travailleurs.

 gpu::for_each(users.begin(),users.end(),update_summary); // all summary is complete now // go access the user-summary here. 

Et gpu::for_each peut attendre que les ouvriers travaillent sur toutes les tâches lambda pour terminer avant d’exécuter les prochaines instructions.

interface fluide

Cela nous permet d’écrire du code lisible de manière concise.

 accounts::erase(std::remove_if(accounts.begin(),accounts.end(),used_this_year)); std::for_each(accounts.begin(),accounts.end(),mark_dormant);