Devrais-je utiliser std :: for_each?

J’essaie toujours d’en savoir plus sur les langues que j’utilise (différents styles, frameworks, 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 d’élargir mon esprit et non d’améliorer le code dans une certaine mesure (lisibilité, expressivité, compacité, etc.).

Donc, avec ce contexte en tête, il est 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; } 

(Le [](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 avait pas vraiment besoin pour le moment mais juste pour mieux comprendre la fonctionnalité pendant un temps qui pourrait en bénéficier il. 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 ancienne école for boucle (ou même la nouvelle boucle C ++ 0x for boucle): vous pouvez regarder 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 plus tôt avant que chaque élément ait été traité 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 qui modifient le stream de contrôle. Il peut comporter des instructions modifiant l’iterator ou la ou les variables d’index. Il n’y a aucun moyen de savoir sans examiner la boucle entière.

Herb Sutter a discuté des avantages de l’utilisation des algorithmes et des expressions lambda dans une présentation récente du groupe d’utilisateurs Northwest C ++ .

Notez que vous pouvez utiliser l’algorithme std::copy ici si vous préférez:

 std::copy(v.begin(), v.end(), std::ostream_iterator(std::cout, "\n")); 

Ça dépend.

La puissance de for_each est que vous pouvez l’utiliser avec n’importe quel conteneur dont les iterators répondent au concept de l’iterator d’entrée et, en tant que tel, il est utilisable de manière générique sur n’importe quel conteneur. Cela augmente la facilité de maintenance de manière à ce que vous puissiez simplement échanger le conteneur et ne pas avoir à changer quoi que ce soit. La même chose ne vaut pas pour une boucle sur la size d’un vecteur. Le seul autre conteneur avec lequel vous pourriez échanger sans avoir à modifier la boucle serait un autre à access aléatoire.

Maintenant, si vous tapez la version de l’iterator vous-même, la version type ressemble à ceci:

 // substitute 'container' with a container of your choice for(std::container::iterator it = c.begin(); it != c.end(); ++it){ // .... } 

Plutôt long, hein? C ++ 0x nous libère de cette longueur avec le mot auto clé auto :

 for(auto it = c.begin(); it != c.end(); ++it){ // .... } 

Déjà mieux, mais toujours pas parfait. Vous appelez à la end de chaque itération et cela peut être mieux fait:

 for(auto it = c.begin(), ite = c.end(); it != ite; ++it){ // .... } 

Ça va bien maintenant Toujours plus long que l’équivalent de la version for_each :

 std::for_each(c.begin(), c.end(), [&](T& item){ // ... }); 

Avec “équivalent” étant légèrement subjectif, comme le T dans la liste de parameters du lambda pourrait être un type verbeux comme my_type::nested_type . Cependant, on peut typedef son chemin autour de cela. Honnêtement, je ne comprends toujours pas pourquoi les lambda n’étaient pas autorisées à être polymorphes avec la déduction de type …


Maintenant, 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: puisque for_each est destiné à parcourir toute la séquence et à appliquer une opération sur chaque élément du conteneur, il n’est pas conçu pour gérer les return anticipés ou les break en général. continue peut être simulé avec une déclaration de return du lambda / functor.

Donc, utilisez for_each où vous voulez vraiment appliquer une opération sur chaque élément de la collection.

Sur un côté, for_each pourrait simplement être “obsolète” avec C ++ 0x grâce aux superbes boucles for range (également appelées boucles foreach):

 for(auto& item : container){ // ... } 

Ce qui est beaucoup plus court (yay) et permet les trois options de:

  • revenir tôt (même avec une valeur de retour!)
  • sortir de la boucle et
  • sauter certains éléments.

Je recommande généralement l’utilisation de std::for_each . Votre exemple de boucle ne fonctionne pas pour les conteneurs à access non aléatoire. Vous pouvez écrire la même boucle à l’aide d’iterators, mais cela est généralement difficile à cause de l’écriture de std::SomeContainerName::const_iterator comme type de variable d’itération. std::for_each vous isole de cela et amortit également l’appel pour qu’il se 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 l’algorithme le plus général qui parcourt une séquence, et donc le moins expressif. Si l’objective de l’itération peut être exprimé en termes de transform , d’ accumulate , de copy , je pense qu’il est préférable d’utiliser l’algorithme spécifique plutôt que le générique for_each .

Avec la nouvelle plage C ++ 0x pour (supscope dans gcc 4.6.0, essayez ceci!), for_each pourrait même perdre son créneau d’être le moyen le plus générique d’appliquer une fonction à une séquence.

Vous pouvez utiliser for cadrage en boucle 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.

Il fonctionne pour tous les conteneurs en STL et tableaux classiques.

Eh bien … cela fonctionne, 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 d’algorithmes standard. Pour votre exemple, vous pouvez écrire:

 boost::for_each(v, [](int n) { cout << n << endl; }); 

(ou boost::copy avec un iterator ostream comme suggéré dans d'autres réponses).

Notez que l’exemple “traditionnel” est buggé:

 for(int i=0; i 

Cela suppose que int peut toujours représenter l'index de chaque valeur du vecteur. Il y a en fait deux façons dont cela peut aller mal.

La première est que int peut être de rang inférieur à std::vector::size_type . Sur une machine 32 bits, les s int sont généralement de 32 bits mais v.size() aura certainement une largeur de 64 bits. Si vous parvenez à placer 2 ^ 32 éléments dans le vecteur, votre index n'atteindra jamais la fin.

Le second problème est que vous comparez une valeur signée ( int ) à une valeur non signée ( std::vector::size_type ). Donc, même si elles étaient du même rang, lorsque la taille dépasse la valeur maximale de l'entier, l'index débordera et déclenchera un comportement indéfini.

Vous pouvez avoir une connaissance préalable 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 les avait rendus possibles.)

Donc, oui, for_each (ou tout autre approprié) est préférable car il évite cet abus pernicieux de int s. Vous pouvez également utiliser une boucle basée sur une plage ou une boucle basée sur un iterator avec auto.

Un avantage supplémentaire de l'utilisation des ou des iterators de plutôt que des index est qu'il vous donne plus de flexibilité pour modifier les types de conteneur à l'avenir sans refactoring tout le code qui l'utilise.