J'essaie toujours d'en apprendre plus sur les langues que j'utilise (différents styles, cadres, modèles, etc.). J'ai remarqué que je n'utilisais jamais std::for_each
alors j'ai pensé que je devrais peut-être commencer. Le but dans de tels cas est de développer mon esprit et not d'améliorer le code dans une certaine mesure (lisibilité, expressivité, compacité, etc.).
Dans ce contexte, il est donc judicieux d’utiliser std::for_each
pour des tâches simples comme, par exemple, imprimer un vecteur:
for_each(v.begin(), v.end(), [](int n) { cout << n << endl; }
(La [](int n)
étant une fonction lambda). Au lieu de:
for(int i=0; i<v.size(); i++) { cout << v[i] << endl; }
J'espère que cette question ne semble pas inutile. Je suppose que cela pose presque une question plus large ... un programmeur intermédiaire devrait-il utiliser une fonctionnalité de langage même s’il n’en a pas vraiment besoin à cette fois} mais juste pour qu’il puisse mieux comprendre la fonctionnalité pendant un moment cela peut en bénéficier grandement. Bien que cette question plus vaste ait probablement déjà été posée (par exemple, ici ).
Il y a un avantage à utiliser std::for_each
au lieu d'une boucle old school for
(ou même de la boucle new_pngled C++ 0x range -for
): vous pouvez consulter le premier mot de l'instruction et savoir exactement ce que fait l'instruction.
Lorsque vous voyez le for_each
, vous savez que l'opération dans le lambda est effectuée exactement une fois pour chaque élément de la plage (en supposant qu'aucune exception ne soit levée). Il n'est pas possible de sortir de la boucle à un stade précoce avant le traitement de chaque élément et il n'est pas possible de sauter des éléments ou d'évaluer le corps de la boucle pour un élément plusieurs fois.
Avec la boucle for
, vous devez lire le corps entier de la boucle pour savoir ce qu’elle fait. Il peut contenir des instructions continue
, break
ou return
modifiant le flux de contrôle. Il peut contenir des instructions modifiant les variables d'itérateur ou d'index. Il n'y a aucun moyen de savoir sans examiner toute la boucle.
Herb Sutter a présenté les avantages de l'utilisation d'algorithmes et d'expressions lambda dans une présentation récente au groupe d'utilisateurs du C++ du Nord-Ouest .
Notez que vous pouvez réellement utiliser l'algorithme std::copy
ici si vous préférez:
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, "\n"));
Ça dépend.
La puissance de for_each
réside dans le fait que vous pouvez l’utiliser avec n’importe quel conteneur dont les itérateurs répondent au concept d’itérateur d’entrée et qu’il est donc utilisable de manière générale sur n’importe quel conteneur. Cela augmente la facilité de maintenance de manière à ce que vous puissiez simplement échanger le conteneur sans rien changer. La même chose ne vaut pas pour une boucle sur le size
d'un vecteur. Les seuls autres conteneurs avec lesquels vous pourriez l'échanger sans avoir à changer de boucle seraient les suivants: un autre à accès aléatoire.
Maintenant, si vous tapez vous-même la version de l'itérateur, la version typique ressemble à ceci:
// substitute 'container' with a container of your choice
for(std::container<T>::iterator it = c.begin(); it != c.end(); ++it){
// ....
}
Assez long, hein? C++ 0x nous évite ce problème de longueur avec le mot clé auto
:
for(auto it = c.begin(); it != c.end(); ++it){
// ....
}
Déjà plus gentil, mais toujours pas parfait. Vous appelez end
à chaque itération et cela peut être amélioré:
for(auto it = c.begin(), ite = c.end(); it != ite; ++it){
// ....
}
Ça a l'air bien maintenant. Toujours plus long que la version for_each
équivalente:
std::for_each(c.begin(), c.end(), [&](T& item){
// ...
});
"Équivalent" étant légèrement subjectif, car T
dans la liste de paramètres du lambda pourrait être un type prolixe tel que my_type<int>::nested_type
. Cependant, on peut typedef
y remédier. Honnêtement, je ne comprends toujours pas pourquoi les lambdas n'étaient pas autorisés à être polymorphes avec déduction de type ...
Une autre chose à considérer est que for_each
, le nom lui-même, exprime déjà une intention. Il dit qu'aucun élément de la séquence ne sera ignoré, ce qui pourrait être le cas avec votre boucle for normale.
Cela m’amène à un autre point: étant donné que for_each
est destiné à exécuter la totalité de la séquence et à appliquer une opération sur l’élément every du conteneur, il n’est pas conçu pour traiter plus tôt return
s ou break
s. continue
peut être simulé avec une instruction return
du lambda/functor.
Donc, utilisez for_each
où vous vraiment voulez appliquer une opération sur l'élément {tous} _ de la collection.
Sur une note de côté, for_each
pourrait bien être "déconseillé" avec C++ 0x grâce aux superbes boucles for basées sur la plage (également appelées boucles foreach):
for(auto& item : container){
// ...
}
Ce qui est beaucoup plus court (yay) et permet aux trois options de:
Je recommanderais généralement l'utilisation de std::for_each
. Votre exemple de boucle ne fonctionne pas pour les conteneurs à accès non aléatoire. Vous pouvez écrire la même boucle en utilisant des itérateurs, mais il est généralement pénible d'écrire std::SomeContainerName<SomeReallyLongUserType>::const_iterator
comme type de variable d'itération. std::for_each
vous en isole et amortit également l'appel à end
automatiquement.
IMHO, vous devriez essayer ces nouvelles fonctionnalités dans votre code de test .
Dans le code de production , vous devriez essayer les fonctionnalités avec lesquelles vous vous sentez à l'aise. (c.-à-d. si vous vous sentez à l'aise avec for_each
, vous pouvez l'utiliser.)
for_each
est le plus général des algorithmes qui parcourent une séquence, et donc le moins expressif. Si l'objectif de l'itération peut être exprimé en termes de transform
, accumulate
, copy
, j'estime qu'il est préférable d'utiliser l'algorithme spécifique plutôt que le for_each
générique.
Avec la nouvelle plage C++ 0x pour (prise en charge dans gcc 4.6.0, essayez-le!), for_each
risque même de perdre son créneau, à savoir le moyen le plus générique d’appliquer une fonction à une séquence.
Vous pouvez utiliser la variable for
loop scope C++ 11
Par exemple:
T arr[5];
for (T & x : arr) //use reference if you want write data
{
//something stuff...
}
Où T est chaque type que vous voulez.
Cela fonctionne pour tous les conteneurs de tableaux STL et classiques.
Eh bien ... ça marche, mais pour imprimer un vecteur (ou le contenu d'autres types de conteneurs), je préfère ceci:
std::copy(v.begin(), v.end(), std::ostream_iterator< int >( std::cout, " " ) );
Boost.Range simplifie l'utilisation des algorithmes standard. Pour votre exemple, vous pouvez écrire:
boost::for_each(v, [](int n) { cout << n << endl; });
(ou boost::copy
avec un itérateur ostream comme suggéré dans d'autres réponses).
Notez que l'exemple "traditionnel" est buggy:
for(int i=0; i<v.size(); i++) { cout << v[i] << endl; }
Cela suppose que int
puisse toujours représenter l'index de chaque valeur du vecteur. En fait, il y a deux façons pour que cela puisse aller mal.
Le premier est que int
peut être de rang inférieur à std::vector<T>::size_type
. Sur une machine 32 bits, int
s ont généralement une largeur de 32 bits, mais v.size()
aura presque certainement une largeur de 64 bits. Si vous parvenez à insérer 2 ^ 32 éléments dans le vecteur, votre index n'atteindra jamais la fin.
Le deuxième problème est que vous comparez une valeur signée (int
) à une valeur non signée (std::vector<T>::size_type
). Ainsi, même s'ils étaient du même rang, lorsque la taille dépasse la valeur entière maximale, l'index débordera et déclenchera un comportement indéfini.
Vous savez peut-être que, pour ce vecteur , ces conditions d'erreur ne seront jamais vraies. Mais vous devez soit ignorer, soit désactiver les avertissements du compilateur. Et si vous les désactivez, vous ne bénéficiez pas de ces avertissements pour vous aider à trouver des bogues réels ailleurs dans votre code. (J'ai passé beaucoup de temps à rechercher les bogues qui auraient dû être détectés par ces avertissements du compilateur, si le code avait permis de les activer.)
Donc, oui, for_each
(ou tout <algorithm>
approprié) est préférable car il évite cet abus pernicieux de int
s. Vous pouvez également utiliser une boucle for basée sur une plage ou une boucle basée sur iterator avec auto.
L’utilisation de <algorithm>
s ou d’itérateurs plutôt que d’index présente un autre avantage: elle vous donne plus de flexibilité pour modifier les types de conteneur à l’avenir sans refactoriser tout le code qui l’utilise.