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?
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.
Voici quelques raisons:
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.)
Il vous permet d’écrire un algorithme sur for_each qui fonctionne avec n’importe quel itérateur.
Cela réduit les risques de bogues de frappe stupides.
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?
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.
Personnellement, chaque fois que je devrais utiliser std::for_each
(écrire des foncteurs spéciaux/boost::lambda
s 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)));
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.
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;
}
}
}
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
.
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 =)
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.
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.
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.
Si vous utilisez fréquemment d'autres algorithmes de la STL, for_each
présente plusieurs avantages:
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:
for_each
.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.)
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); }
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.
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.
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.
for_each
permet d'implémenter motif Fork-Join . Autre que cela, il supporte fluent-interface .
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.
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);
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 :)
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/