web-dev-qa-db-fra.com

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 ne semble que gêner la lisibilité du code. Pourquoi certaines normes de codage recommandent-elles son utilisation? 

147
missingfaktor

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

Je veux dire, personne dans son esprit, qui veut parcourir toute une collection, utilisera encore 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 range-based for loop est disponible:

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

Ce type de syntaxe est disponible dans Java et C # depuis quelque temps déjà, et en réalité, il y a beaucoup plus de boucles foreach que de boucles for classiques dans chaque code Java ou C # récent que j'ai vu.

160
Thomas Petit

Voici quelques raisons:

  1. Cela semble entraver la lisibilité simplement parce que vous n'y êtes pas habitué et/ou que vous n'utilisez pas les outils appropriés pour simplifier les choses. (voir boost :: range et boost :: bind/boost :: lambda pour les assistants. Beaucoup d'entre eux iront en C++ 0x et rendront plus utiles for_each et les fonctions associées.)

  2. Il vous permet d’écrire un algorithme sur for_each qui fonctionne avec n’importe quel itérateur. 

  3. Cela réduit les risques de bogues de frappe stupides.

  4. Cela ouvre également votre esprit au reste des algorithmes STL, comme find_if, sort, replace, etc. et ceux-ci n'auront plus l'air si étrange. Cela peut être une victoire énorme.

Mise à jour 1:

Plus important encore, cela vous aide à aller au-delà de for_each plutôt que des boucles for-like et à regarder les autres styles STL, tels que find/sort/partition/copy_replace_if, une exécution parallèle, etc.

Beaucoup de traitements peuvent être écrits de manière très concise en utilisant "le reste" de la fratrie de for_each, mais si vous ne faites que créer une boucle for avec plusieurs logiques internes, vous ne saurez jamais comment les utiliser. finissent par inventer la roue encore et encore. 

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

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

Ou avec les lambdas C++ x11:

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

l’OMI est-il plus lisible que:

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

Aussi ceci (ou avec les lambdas, voir les autres):

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

Est plus concis que:

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

Surtout si vous avez plusieurs fonctions à appeler dans l'ordre ... mais peut-être que c'est juste moi. ;)

Mise à jour 2 : J'ai écrit mes propres wrappers one-liner de stl-algos qui fonctionnent avec des plages au lieu de deux itérateurs. boost :: range_ex, une fois publié, inclura cela et peut-être que ce sera là aussi dans C++ 0x?

46
Macke

for_each est plus générique. Vous pouvez l'utiliser pour itérer sur n'importe quel type de conteneur (en passant par les itérateurs de début/fin). Vous pouvez potentiellement échanger des conteneurs sous une fonction qui utilise for_each sans avoir à mettre à jour le code d'itération. Vous devez prendre en compte le fait qu'il existe dans le monde d'autres conteneurs que std :: vector et tout simplement les vieux tableaux C pour voir les avantages de for_each.

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

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

Cela ne vous semblera pas bizarre dans 3 ans.

22
Terry Mahaffey

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

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

c'est très subjectif, certains diront que l'utilisation de for_each rendra le code plus / lisible, car cela permet de traiter différentes collections avec les mêmes conventions. for_each itslef est implémenté sous forme de boucle

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

alors à vous de choisir ce qui vous convient le mieux. 

11
Alon

Comme beaucoup de fonctions de l'algorithme, une réaction initiale est de penser qu'il est plus illisible d'utiliser foreach qu'une boucle. Cela a été un sujet de nombreuses guerres de flammes.

Une fois que vous vous serez habitué à l'idiome, vous pourrez 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 découper le code sans réel avantage).

Un autre avantage est que quand je vois pour chaque, je savoir soit chaque objet sera traité, soit une exception sera levée.

UNE for boucle permet plusieurs options pour terminer la boucle. Vous pouvez laisser la boucle suivre son cours complet ou utiliser le pause mot-clé pour sauter explicitement de la boucle ou utiliser le revenir mot-clé pour quitter la totalité de la fonction en milieu de boucle. En revanche, pour chaque 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 de l'itération.

Voici un exemple de confusion for boucle:

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;
      }
   }
}
10
Andrew Shepherd

La plupart du temps, vous avez raison: std::for_each est une perte nette. J'irais même jusqu'à comparer for_each à goto. goto fournit le contrôle de flux le plus polyvalent possible. Vous pouvez l'utiliser pour mettre en œuvre pratiquement n'importe quelle autre structure de contrôle que vous pouvez imaginer. Cette très grande polyvalence, cependant, signifie que le fait de voir une goto de manière isolée vous indique pratiquement rien ce qu’elle est destinée à faire dans cette situation. En conséquence, presque personne sensé n'utilise goto qu'en dernier recours.

Parmi les algorithmes standard, for_each est à peu près identique - il peut être utilisé pour pratiquement tout implémenter, ce qui signifie que l'affichage de for_each ne vous indique pratiquement pas à quoi il sert dans cette situation. Malheureusement, l’attitude des gens à l’égard de for_each indique où en est leur attitude à l’égard de goto environ (en 1970 environ). Un peuconsidérez-le toujours comme l’algorithme principal, et utilisez rarement, sinon jamais, un autre. La plupart du temps, un simple coup d'œil révélerait qu'une des solutions de rechange était radicalement supérieure.

Par exemple, je suis presque sûr d'avoir perdu la trace du nombre de fois où j'ai vu des personnes écrire du code pour imprimer le contenu d'une collection à l'aide de for_each. D'après les publications que j'ai vues, il pourrait s'agir de l'utilisation la plus courante de for_each. Ils se retrouvent 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 doivent créer quelque chose comme:

std::vector<XXX> 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 à ce qui est passe entre les pièces qui le tiennent ensemble.

Heureusement, il existe un bien meilleur moyen. Ajoutez une surcharge d'insertion de flux 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<XXX>(std::cout, "\n"));

Cela fonctionne - et ne demande pratiquement aucun travail pour savoir qu’il imprime le contenu de coll à std::cout.

9
Jerry Coffin

L'avantage d'écrire fonctionnel pour être plus lisible, peut ne pas apparaître lorsque 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 je pense être très gentil, généralisez les boucles pour les fonctions à une ligne =)

8
Viktor Sehr

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

En outre, la boucle à plage for itère uniquement du début à la fin des conteneurs entiers, alors que for_each est plus flexible.

5
Tigran Saluev

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

Le problème de lisibilité dans la norme actuelle est qu’elle 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 objets foncteur 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<int> 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 objet, 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 les choses:

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

Qui n'est pas moins lisible et plus compact que la version à la main si vous avez une fonction/méthode à appeler en place. L'implémentation pourrait fournir d'autres implémentations de la boucle for_each (pensez au traitement parallèle).

La prochaine norme prend en charge certaines des lacunes de différentes manières. Elle autorisera l'utilisation de classes définies localement comme arguments des modèles:

void apply( std::vector<int> 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 ici. En fait, vous n'avez même pas besoin d'utiliser la syntaxe de la classe pour définir le foncteur, mais utilisez un lambda à cet endroit:

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

Même si pour le cas de for_each, il y aura une construction spécifique qui le rendra plus naturel:

void apply( std::vector<int> 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 j'ai seulement besoin d'un appel à une fonction ou à une méthode existante (for_each( v.begin(), v.end(), boost::bind( &Type::update, _1 ) )), je choisis la construction for_each qui enlève au code beaucoup de choses sur l'itérateur de la plaque de la chaudière. Lorsque 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 de code non critiques, je pourrais aller avec BOOST_FOREACH (un collègue me l'a fait entrer)

J'avais l'habitude de ne pas aimer std::for_each et je pensais que sans lambda, tout était mal fait. Cependant, j'ai changé d'avis il y a quelque temps, et maintenant je l'aime réellement. 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 faire quelque chose avec tous les éléments de la plage , qui peut améliorer la lisibilité. Supposons que l'action que vous souhaitez effectuer dure 20 lignes et que la fonction où l'action est exécutée dure également environ 20 lignes. Cela ferait une fonction longue de 40 lignes avec une boucle for conventionnelle, et seulement environ 20 avec std::for_each, donc probablement plus facile à comprendre.

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

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

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

Tous ces foncteurs sont normalement plus faciles à obtenir avec des tests unitaires qu’une boucle for 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 faire une erreur avec la plage.

Enfin, le compilateur peut produire un code légèrement meilleur pour std::for_each que pour certains types de boucles créées à la main, car il (for_each) always est identique pour le compilateur, et les auteurs de compilateur peuvent mettre toutes leurs connaissances aussi bien que possible.

La même chose s'applique à d'autres algorithmes std comme find_if, transform etc.

3
Dmitry

Outre la lisibilité et les performances, l'un des aspects généralement négligé est la cohérence. Il existe plusieurs façons d'implémenter une boucle for (ou while) sur des itérateurs, à 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 entre deux à différents niveaux d'efficacité et de potentiel de bug.

Cependant, utiliser for_each impose la cohérence en faisant abstraction de la boucle:

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

La seule chose qui vous préoccupe maintenant est la suivante: implémentez-vous le corps de la boucle en tant que fonction, foncteur ou lambda à l’aide de fonctionnalités Boost ou C++ 0x? Personnellement, je préfère m'inquiéter à ce sujet que de savoir comment mettre en œuvre ou lire une boucle aléatoire pour/tout.

3
Jason Govig

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 de cette interface et en partie parce que c'est en fait un peu plus concis.
  2. Bien qu'une boucle for basée sur une plage puisse être encore plus simple, elle est moins flexible (comme l'a noté Adrian McCarthy, elle itère sur un conteneur entier).
  3. Contrairement à une boucle for traditionnelle, for_each vous oblige à écrire du code qui fonctionnera pour tout itérateur d’entrée. Être limité de cette manière peut être une bonne chose pour les raisons suivantes:

    1. Vous devrez peut-être adapter le code pour pouvoir utiliser ultérieurement 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, d'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 le fait que vous pouvez utiliser 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, for_each est la meilleure option, mais une boucle for n'est pas la seule alternative.)

2
Sean Patrick Santos

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'y est pas.

Pour moi, for_each a toujours été meilleur pour les mêmes raisons lorsque le corps de la boucle est déjà un foncteur, et je tirerai le meilleur parti possible.

Vous utilisez toujours la for à trois expressions, mais lorsque vous en voyez une, vous savez qu'il y a quelque chose à comprendre, ce n'est pas un passe-partout. Je déteste passe-partout. Je m'en veux de son existence. Ce n'est pas du vrai code, il n'y a rien à apprendre en le lisant, c'est juste une chose à vérifier. L’effort mental peut être mesuré par la facilité avec laquelle il est facile de le vérifier.

Les modèles sont

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

for est une boucle permettant d'itérer chaque élément ou tous les trois etc. etc. for_each sert uniquement à itérer chaque élément. C'est clair de son nom. Il est donc plus clair ce que vous avez l'intention de faire dans votre code.

2

Surtout vous devrez parcourir l'ensemble de la collection. Par conséquent, je vous suggère d'écrire votre propre variante for_each (), en ne prenant que 2 paramètres. Cela vous permettra de réécrire l'exemple de Terry Mahaffey comme:

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

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

1
Dimitri C.

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

Je préfère utiliser

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

Je trouve un explicite de boucle explicite à lire et l'utilisation explicite de variables nommées pour les itérateurs de début et de fin réduit l'encombrement dans la boucle for.

Bien sûr, les cas varient, c’est ce que je trouve généralement préférable.

1
jcoder

for_each permet d'implémenter motif Fork-Join . Autre que cela, il supporte fluent-interface

modèle de jointure à la fourche

Nous pouvons ajouter l'implémentation gpu::for_each pour utiliser cuda/gpu pour l'informatique parallèle hétérogène en appelant la tâche lambda dans plusieurs opérateurs.

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 déclarations.

interface fluide

Cela nous permet d’écrire du code lisible par l’homme 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);
0
shuva

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 .__ Assurez-vous de lire les commentaires aussi :)

0
NoSenseEtAl

Vous pouvez demander à l'itérateur d'appeler une fonction exécutée à chaque itération de la boucle.

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

0
sdornan