Avantages de std :: for_each sur la boucle for

168

Y a-t-il des avantages de la std::for_eachsur- forboucle? Pour moi, cela std::for_eachsemble seulement nuire à la lisibilité du code. Pourquoi alors certaines normes de codage recommandent-elles son utilisation?

manquant
la source
10
std::for_eachlorsqu'il est utilisé avec boost.lambdaou boost.bindpeut souvent améliorer la lisibilité
La question et la réponse acceptée datent de 2010. Pour une réponse plus à jour (à partir de 2018), voir ici: fluentcpp.com/2018/03/30/is-stdfor_each-obsolete
Erel Segal-Halevi

Réponses:

181

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

Je veux dire, personne de sensé, qui souhaite parcourir une collection entière, ne l'utilisera toujours

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 la boucle basée surfor la plage est disponible:

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

Ce type de syntaxe est disponible en Java et C # depuis un certain temps maintenant, et en fait, il y a beaucoup plus de foreachboucles que de forboucles classiques dans chaque code Java ou C # récent que j'ai vu.

Thomas Petit
la source
12
En fait, une boucle foreach avec scoop est disponible depuis longtemps dans boost, et je veux toujours itérer avec for_each et une fonction lambda.
Viktor Sehr
19
L'hypothèse de vouloir la totalité de la plage de conteneurs ne fait pas partie de la question, il ne s'agit donc que d'une réponse partielle.
Adrian McCarthy
6
Notez que faire une boucle sur un élément n'est probablement pas la seule chose que vous voulez faire, donc cela peut être une bonne idée d'utiliser for_each juste pour en savoir plus sur find / partition / copy_replace_if et les autres, ce qui est beaucoup pour les boucles en fait faire.
Macke
10
Range-for est bien, sauf lorsque vous avez réellement besoin de l'itérateur (alors il n'y a aucun moyen d'y accéder).
Damon
5
Je n'utiliserais même pas Element & ecomme auto & e(ou auto const &e) mieux. J'utiliserais Element const e(sans référence) lorsque je veux une conversion implicite, par exemple lorsque la source est une collection de types différents, et que je veux les convertir en Element.
Nawaz
54

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 for_each et les fonctions associées plus utiles.)

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

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

  4. Il ouvre également votre esprit pour le reste de la STL-algorithmes, comme find_if, sort, replace, etc et ceux - ci ne regardera plus si étrange. Cela peut être une énorme victoire.

Mise à jour 1:

Plus important encore, cela vous aide à aller au-delà for_eachdes boucles for comme c'est tout ce qu'il y a, et à regarder les autres STL-alogs, comme find / sort / partition / copy_replace_if, l'exécution parallèle ... ou autre.

Un grand nombre de traitements peuvent être écrits de manière très concise en utilisant "le reste" des frères et sœurs de for_each, mais si tout ce que vous faites est d'écrire une boucle for avec diverses logiques internes, vous n'apprendrez jamais à les utiliser, et vous finissent par inventer la roue encore et encore.

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

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

Ou avec des lambdas C ++ x11:

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

L'OMI est-elle plus lisible que:

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

Aussi ceci (ou avec des lambdas, voir 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 ce n'est que moi. ;)

Mise à jour 2 : J'ai écrit mes propres wrappers à une ligne de stl-algos qui fonctionnent avec des plages au lieu de paires d'itérateurs. boost :: range_ex, une fois publié, inclura-t-il cela et peut-être sera-t-il également présent dans C ++ 0x?

Macke
la source
+1, plusieurs fonctions ou types imbriqués: outer_class::inner_class::iteratorou ce sont des arguments de modèle: typename std::vector<T>::iterator... la construction for elle-même peut se
heurter
4
(btw: le for_eachdans le deuxième exemple est incorrect (devrait êtrefor_each( bananas.begin(), bananas.end(),...
David Rodríguez - dribeas
J'ai écrit des wrappers qui utilisent des plages au lieu de deux itérateurs. Ceux-ci seront disponibles plus tard (voir range_ex) mais tout le monde devrait les avoir de toute façon. (Mise à jour ajoutée à ce sujet.)
Macke
1
La prise en charge du traitement parallèle est non. 1 raison ici. Nous pouvons ajouter une implémentation pour utiliser cuda / gpu pour le calcul parallèle hétérogène.
shuva
25

for_eachest plus générique. Vous pouvez l'utiliser pour itérer sur n'importe quel type de conteneur (en passant les itérateurs de début / fin). Vous pouvez potentiellement échanger des conteneurs sous une fonction qui utilise for_eachsans avoir à mettre à jour le code d'itération. Vous devez considérer qu'il existe d'autres conteneurs dans le monde que les std::vectoranciens tableaux C simples pour voir les avantages de for_each.

L'inconvénient majeur de for_eachest qu'il faut un foncteur, donc la syntaxe est maladroite. Ceci est corrigé dans C ++ 11 (anciennement 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.

Terry Mahaffey
la source
3
+1. Annonce lorsque for_each prend un conteneur / une plage au lieu de deux itérateurs, c'est toujours plus génial.
Macke
2
@Marcus: ce sera la construction à distance et la syntaxe ne lira pas 'for_each' en soi: for ( int v : int_vector ) {(même si elle peut être simulée aujourd'hui avec BOOST_FOREACH)
David Rodríguez - dribeas
@David: Je fais référence à l'ajout général de fonctions basées sur les plages (vous pouvez donc utiliser des plages avec toutes ces fonctions for_each, copy, remove_if, etc etc),
Macke
1
Pourquoi est - il pas possible d'écrire: std::for_each(container, [](int& i){ ... });. Je veux dire pourquoi on est obligé d'écrire un conteneur deux fois?
Giorgio
1
@freitass: écrire le conteneur une fois comme dans mon commentaire précédent pourrait utiliser par défaut l'itérateur de début et de fin sans les appeler explicitement. La plupart des langages qui fournissent des fonctions d'ordre supérieur sur les collections (Ruby, Scala, ...) écrivent quelque chose comme container.each { ... }sans mentionner les itérateurs de début et de fin. Je trouve un peu redondant que je doive spécifier l'itérateur de fin tout le temps.
Giorgio
18

Personnellement, chaque fois que je devrais faire tout mon possible pour utiliser std::for_each(écrire des foncteurs spéciaux / des boost::lambdas compliqués ), je trouve BOOST_FOREACHet C ++ 0x basé 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)));
OncleBens
la source
12

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

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

c'est donc à vous de choisir ce qui vous convient.

Alon
la source
11

Comme beaucoup de fonctions de l'algorithme, une première réaction 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 êtes habitué à l'idiome, vous pouvez le trouver utile. Un avantage évident est qu'il 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 êtes juste en train de découper le code sans réel avantage).

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

Une boucle for permet plusieurs options pour terminer la boucle. Vous pouvez laisser la boucle suivre son cours complet, ou vous pouvez utiliser le mot-clé break pour sauter explicitement hors de la boucle, ou utiliser le mot-clé return pour quitter toute la fonction à mi-boucle. En revanche, foreach n'autorise pas ces options, ce qui le rend plus lisible. Vous pouvez simplement regarder le nom de la fonction et vous connaissez la nature complète de l'itération.

Voici un exemple de boucle for déroutante :

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;
      }
   }
}
Andrew Shepherd
la source
1
Vous faites un bon point, mais votre exemple de motivation n'est pas tout à fait juste. Lorsque vous utilisez std::for_each()dans l'ancien standard (celui de l'époque de ce post) vous devez utiliser un foncteur nommé, qui encourage la lisibilité comme vous le dites et interdit de sortir prématurément de la boucle. Mais alors la forboucle équivalente n'a rien d'autre qu'un appel de fonction, et cela aussi interdit une rupture prématurée. Mais à part cela, je pense que vous avez fait un excellent argument en disant que cela std::for_each() impose de passer par toute la gamme.
wilhelmtell
10

Vous avez généralement raison: la plupart du temps, std::for_eachc'est une perte nette. J'irais jusqu'à comparer for_eachà goto. gotofournit le contrôle de flux le plus polyvalent possible - vous pouvez l'utiliser pour implémenter pratiquement toute autre structure de contrôle que vous pouvez imaginer. Cette polyvalence même, cependant, signifie que voir un gotoisolé ne vous dit pratiquement rien sur ce qu'il est censé faire dans cette situation. En conséquence, presque personne saine d'esprit ne l'utilise gotoqu'en dernier recours.

Parmi les algorithmes standard, for_eachc'est à peu près la même chose - il peut être utilisé pour implémenter pratiquement n'importe quoi, ce qui signifie que voir ne for_eachvous dit pratiquement rien sur ce à quoi il est utilisé dans cette situation. Malheureusement, l'attitude des gens à l'égard de la question for_eachest de savoir où était leur attitude envers goto(disons) 1970 environ - quelques personnes avaient compris qu'il ne devrait être utilisé qu'en dernier recours, mais beaucoup le considèrent toujours comme l'algorithme principal, et rarement, voire jamais, en utiliser d’autre. La grande majorité du temps, même un rapide coup d'œil révélerait que l'une des alternatives était radicalement supérieure.

Par exemple, je suis à peu près sûr que j'ai perdu la trace du nombre de fois où j'ai vu des gens écrire du code pour imprimer le contenu d'une collection en utilisant for_each. D'après les articles que j'ai vus, cela pourrait bien être 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 poste demande à ce que la combinaison de bind1st, mem_funetc. ils ont besoin de faire quelque chose comme:

std::vector<XXX> coll;

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

work et imprimez les éléments de coll. Si cela fonctionnait vraiment exactement comme je l'ai écrit là-bas, ce serait médiocre, mais ce n'est pas le cas - et au moment où vous l'avez fait fonctionner, il est difficile de trouver ces quelques morceaux de code liés à ce qui passe parmi les pièces qui le maintiennent 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 nécessite pratiquement aucun travail pour comprendre qu'il imprime le contenu de collto std::cout.

Jerry Coffin
la source
+1, il y a cependant une erreur. Dans le premier exemple, il devrait être boost::mem_fn(&XXX::print)plutôt queXXX::print
missingfaktor
C'est pourquoi j'ai dit que cet exemple ne fonctionnera pas, et ils postent pour demander de l'aide pour le faire fonctionner (oh, et vous devez également lier std::coutcomme argument pour que cela fonctionne).
Jerry Coffin du
1
Bien que ce soit généralement une bonne réponse, la question ne porte pas sur la valeur de for_each ou sa valeur par rapport à d'autres algorithmes std, mais sur sa valeur par rapport à une boucle for. Vous pouvez y penser dans les cas où aucun autre algorithme std ne s'applique. Utilisez-vous alors une boucle for_each ou for? Pensez-y et quoi que vous fassiez, cela aurait dû être votre réponse.
Christian Rau
@ChristianRau: Il y a toujours une ligne fine entre répondre exactement à la question et essayer de fournir des informations utiles. La réponse directe à exactement les questions qu'il a posées serait «Probablement pas. Qui sait?», Mais serait trop inutile pour valoir la peine. En même temps, aller trop loin (par exemple, recommander Haskell au lieu de tout ce qui précède) ne sera probablement pas non plus d'une grande utilité.
Jerry Coffin
3
@ChristianRau: Comment pensez-vous que "... la plupart du temps, std :: for_each est une perte nette" ne répond pas à la question de savoir si std :: for_each offre un avantage?
Jerry Coffin
8

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 de 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 trouve si gentil, généraliser les boucles for à une ligne fonctions =)

Viktor Sehr
la source
6

Facile: for_eachest utile lorsque vous avez déjà une fonction pour gérer chaque élément du tableau, vous n'avez donc pas à écrire un lambda. Certainement, ce

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

est mieux que

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

De plus, la forboucle à distance n'itère que sur des conteneurs entiers du début à la fin, tout en for_eachétant plus flexible.

Tigran Saluev
la source
4

La for_eachboucle a pour but de masquer les itérateurs (détail de l'implémentation d'une boucle) du code utilisateur et de 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'il nécessite un foncteur comme dernier argument au lieu d'un bloc de code, donc dans de nombreux cas, vous devez écrire un type de foncteur spécifique pour celui-ci. Cela se transforme en code moins lisible car les objets 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<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::binddans la norme suivante), ou boost::lambda(lambdas dans la norme suivante) pour le rendre plus 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
}

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

La norme à venir s'occupe de certaines des lacunes de différentes manières, elle autorisera des classes définies localement comme arguments dans les 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élioration de 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 classe pour définir le foncteur, mais utilisez un lambda juste là:

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

Même si pour le cas for_eachil y aura une construction spécifique qui la rendra plus naturelle:

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

J'ai tendance à mélanger la for_eachconstruction avec des boucles roulées à la main. Quand 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 choisis la for_eachconstruction qui enlève au code beaucoup de trucs d'itérateur de plaque chauffante. 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 utiliser BOOST_FOREACH (un collègue m'a mis dedans)

David Rodríguez - Dribeas
la source
3

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

Cependant, l'utilisation de for_each 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ère m'inquiéter de cela que de savoir comment implémenter ou lire une boucle for / while aléatoire.

Jason Govig
la source
3

J'avais l'habitude de ne pas aimer std::for_eachet je pensais que sans lambda, c'était complètement faux. Cependant, j'ai changé d'avis il y a quelque temps, et maintenant j'adore ça. Et je pense que cela améliore même la lisibilité et facilite le test de votre code de manière TDD.

L' std::for_eachalgorithme peut être lu comme quelque chose avec tous les éléments à portée , ce qui peut améliorer la lisibilité. Supposons que l'action que vous souhaitez effectuer dure 20 lignes et que la fonction dans laquelle l'action est exécutée est également d'environ 20 lignes. Cela rendrait une fonction de 40 lignes avec une boucle for classique, et seulement environ 20 avec std::for_each, donc probablement plus facile à comprendre.

Les fonctions pour std::for_eachsont 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())ce qui est légèrement mieux IMO qu'une boucle explicite.

Tous ces foncteurs sont normalement plus faciles à obtenir dans les 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 portée.

Et enfin, le compilateur peut produire un code légèrement meilleur pour std::for_eachque pour certains types de boucle for artisanale, car il (for_each) a toujours la même apparence pour le compilateur, et les rédacteurs du compilateur peuvent mettre toutes leurs connaissances, pour le rendre aussi bon qu'ils pouvez.

Il en va de même pour d'autres algorithmes std comme find_if, transformetc.

Dmitry
la source
2

forest pour la boucle qui peut itérer chaque élément ou tous les tiers, etc. for_eachest pour itérer seulement chaque élément. Il ressort clairement de son nom. Ce que vous comptez faire dans votre code est donc plus clair.

Kirill V. Lyadvinsky
la source
4
pas si vous lui passez un itérateur qui avance de 3 avec chacun ++. Inhabituel peut-être, mais une boucle for fait de même.
Potatoswatter
Dans ce cas, il vaut peut-être mieux utiliser transformpour ne pas confondre quelqu'un.
Kirill V.Lyadvinsky
2

Si vous utilisez fréquemment d'autres algorithmes de la STL, il existe plusieurs avantages à for_each:

  1. Elle sera souvent plus simple et moins sujette aux erreurs qu'une boucle for, en partie parce que vous serez habitué aux fonctions avec cette interface, et en partie parce qu'elle est en fait un peu plus concise dans de nombreux cas.
  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_eachvous oblige à écrire du code qui fonctionnera pour n'importe quel itérateur d'entrée. Être restreint de cette manière peut en fait être une bonne chose car:

    1. Vous devrez peut-être adapter le code pour qu'il fonctionne pour un autre conteneur ultérieurement.
    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 pourraient ne pas le faire sans être invitées à l'utiliser for_each.
  4. L'utilisation for_eachrend parfois plus évidente 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 qui for_eachest la meilleure option, mais une boucle for n'est pas la seule alternative.)

Sean Patrick Santos
la source
2

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_eachou d'une boucle. Pourquoi le choisir 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_eachc'était toujours mieux sur les mêmes bases lorsque le corps de la boucle est déjà un foncteur, et je vais profiter de tous les avantages que je peux obtenir.

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

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); }
jthill
la source
1

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 ne prenant que 2 paramètres. Cela vous permettra de réécrire l'exemple de Terry Mahaffey comme suit :

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

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

Dimitri C.
la source
1

Je trouve que for_each est mauvais pour la lisibilité. Le concept est bon, mais c ++ rend très difficile l'écriture lisible, du moins pour moi. Les expressions lamda c ++ 0x seront utiles. J'aime vraiment 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% de m'y habituer. Peut-être que dans 5 ans, je me serai habitué et je n'y réfléchirai pas une seconde fois, 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 une boucle for explicite plus claire à lire et explicite en utilisant des 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 exactement ce que je trouve généralement le mieux.

jcoder
la source
0

Vous pouvez faire en sorte que l'itérateur soit un appel à une fonction exécutée à chaque itération de la boucle.

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

sdornan
la source
2
Les publications contenant uniquement des liens ne donnent pas de bonnes réponses, et de toute façon, où dans ce lien montre-t-il quelque chose qui ressemble à un itérateur appelable? Je suis presque sûr que ce concept n'a pas de sens. Peut-être que vous résumiez simplement ce qui for_eachfait, auquel cas, cela ne répond pas à la question sur ses avantages.
underscore_d
0

for_eachnous permettent d'implémenter le modèle Fork-Join . Autre que cela, il prend en charge l' interface fluide .

motif de jointure en fourche

Nous pouvons ajouter une implémentation gpu::for_eachpour utiliser cuda / gpu pour le calcul parallèle hétérogène en appelant la tâche lambda dans plusieurs nœuds de calcul.

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

Et gpu::for_eachpeut attendre que les travailleurs travaillent sur toutes les tâches lambda avant d'exécuter les instructions suivantes.

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);
Shuva
la source