Y a-t-il des avantages de la std::for_each
sur- for
boucle? Pour moi, cela std::for_each
semble seulement nuire à la lisibilité du code. Pourquoi alors certaines normes de codage recommandent-elles son utilisation?
Y a-t-il des avantages de la std::for_each
sur- for
boucle? Pour moi, cela std::for_each
semble seulement nuire à la lisibilité du code. Pourquoi alors certaines normes de codage recommandent-elles son utilisation?
Réponses:
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 foreach
boucles que de for
boucles classiques dans chaque code Java ou C # récent que j'ai vu.
Element & e
comme 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
.
Voici quelques raisons:
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.)
Il vous permet d'écrire un algorithme au-dessus de for_each qui fonctionne avec n'importe quel itérateur.
Cela réduit le risque de bogues de frappe stupides.
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_each
des 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?
outer_class::inner_class::iterator
ou ce sont des arguments de modèle: typename std::vector<T>::iterator
... la construction for elle-même peut se
for_each
dans le deuxième exemple est incorrect (devrait êtrefor_each( bananas.begin(), bananas.end(),...
for_each
est 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_each
sans avoir à mettre à jour le code d'itération. Vous devez considérer qu'il existe d'autres conteneurs dans le monde que les std::vector
anciens tableaux C simples 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 ++ 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.
for ( int v : int_vector ) {
(même si elle peut être simulée aujourd'hui avec BOOST_FOREACH)
std::for_each(container, [](int& i){ ... });
. Je veux dire pourquoi on est obligé d'écrire un conteneur deux fois?
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.
Personnellement, chaque fois que je devrais faire tout mon possible pour utiliser std::for_each
(écrire des foncteurs spéciaux / des boost::lambda
s compliqués ), je trouve BOOST_FOREACH
et 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)));
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_each
itslef 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.
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;
}
}
}
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 for
boucle é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.
Vous avez généralement raison: la plupart du temps, std::for_each
c'est une perte nette. J'irais jusqu'à comparer for_each
à goto
. goto
fournit 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 goto
isolé 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 goto
qu'en dernier recours.
Parmi les algorithmes standard, for_each
c'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_each
vous dit pratiquement rien sur ce à quoi il est utilisé dans cette situation. Malheureusement, l'attitude des gens à l'égard de la question for_each
est 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_fun
etc. 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 coll
to std::cout
.
boost::mem_fn(&XXX::print)
plutôt queXXX::print
std::cout
comme argument pour que cela fonctionne).
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 =)
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 à é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 for
boucle à distance n'itère que sur des conteneurs entiers du début à la fin, tout en for_each
étant plus flexible.
La for_each
boucle 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::bind
dans 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_each
boucle (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_each
il 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_each
construction 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_each
construction 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)
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.
J'avais l'habitude de ne pas aimer std::for_each
et 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_each
algorithme 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_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())
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_each
que 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
, transform
etc.
for
est pour la boucle qui peut itérer chaque élément ou tous les tiers, etc. for_each
est 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.
++
. Inhabituel peut-être, mais une boucle for fait de même.
transform
pour ne pas confondre quelqu'un.
Si vous utilisez fréquemment d'autres algorithmes de la STL, il existe plusieurs avantages à for_each
:
Contrairement à une boucle for traditionnelle, for_each
vous 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:
for_each
.L'utilisation for_each
rend 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_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 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_each
c'é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); }
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.
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.
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/
for_each
fait, auquel cas, cela ne répond pas à la question sur ses avantages.
La boucle For peut se rompre; 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 aussi les commentaires :)
for_each
nous permettent d'implémenter le modèle Fork-Join . Autre que cela, il prend en charge l' interface fluide .
Nous pouvons ajouter une implémentation gpu::for_each
pour 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_each
peut attendre que les travailleurs travaillent sur toutes les tâches lambda avant d'exécuter les instructions suivantes.
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);
std::for_each
lorsqu'il est utilisé avecboost.lambda
ouboost.bind
peut souvent améliorer la lisibilité