web-dev-qa-db-fra.com

Devrais-je utiliser std :: for_each?

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 ).

42
Alan Turing

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"));
46
James McNellis

Ç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 returns ou breaks. 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:

  • retour anticipé (même avec une valeur de retour!)
  • sortir de la boucle et
  • sauter certains éléments.
24
Xeo

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.

9
Billy ONeal

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.)

8
iammilind

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.

3
Cubbi

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.

1
NeroTestero

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, " " ) );
0
BЈовић

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).

0
rafak

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, ints 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 ints. 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.

0
Adrian McCarthy