45 votes

Devrais-je utiliser std::for_each ?

J'essaie toujours d'en apprendre davantage sur les langages que j'utilise (différents styles, cadres, modèles, etc.). J'ai remarqué que je n'utilise jamais std::for_each alors j'ai pensé que je devrais peut-être commencer. Le but dans ces cas-là est d'élargir mon esprit et de no améliorer le code dans une certaine mesure (lisibilité, expressivité, compacité, etc.).

Donc avec ce contexte à l'esprit, est-ce une bonne idée d'utiliser std::for_each pour des tâches simples comme, par exemple, l'impression d'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 vous semblera pas inutile. Je pense qu'elle pose presque une question plus large : un programmeur intermédiaire doit-il utiliser une fonctionnalité du langage même s'il n'en a pas vraiment besoin ? cette fois-ci mais juste pour qu'il puisse mieux comprendre la fonction pour une époque qui pourrait en bénéficier grandement. Bien que cette question plus large ait probablement déjà été posée (par ex. aquí ).

2 votes

Si vous n'êtes pas familier avec une fonctionnalité, comment saurez-vous quand il est approprié de l'utiliser ?

1 votes

Définissez "devrait". Les performances seront presque certainement identiques. La portabilité est identique tant que l'on parle de C++0x. La lisibilité/maintenabilité est dans l'œil de celui qui regarde...

8 votes

Je pense qu'un for_each + lambda s'exprime mieux comme un for basé sur une plage : for (int n : v) { cout << n << endl; }

48voto

James McNellis Points 193607

Il y a un avantage à utiliser std::for_each au lieu d'une vieille école for (ou même la nouvelle boucle d'extension C++0x). for boucle) : vous pouvez regarder le premier mot de l'instruction et vous savez exactement ce que fait l'instruction.

Lorsque vous voyez le for_each vous savez que l'opération dans la lambda est effectuée exactement une fois pour chaque élément de la plage (en supposant qu'aucune exception n'est levée). Il n'est pas possible de sortir de la boucle 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 le for vous devez lire le corps entier de la boucle pour savoir ce qu'elle fait. Elle peut avoir continue , break o return qui modifient le flux de contrôle. Il peut contenir des instructions qui modifient l'itérateur ou les variables d'index. Il n'y a aucun moyen de le savoir sans examiner l'ensemble de la boucle.

Herb Sutter a discuté des avantages de l'utilisation d'algorithmes et d'expressions lambda. dans une récente présentation au groupe d'utilisateurs de C++ du Nord-Ouest .

Notez que vous pouvez en fait utiliser l'option std::copy algorithme ici si vous préférez :

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

2 votes

Et puisque l'auteur de la question dispose de C++0x, vous pourriez probablement faire ce qui suit std::ostream_iterator<decltype(*v.begin())> . Je ne sais pas si j'aime ça, mais il se peut que je remplace une gêne par une autre.

3 votes

L'équivalent de continue sur for_each est juste return .

25voto

Xeo Points 69818

Ça dépend.

Le pouvoir de for_each c'est que vous pouvez l'utiliser avec n'importe quel conteneur dont les itérateurs satisfont au concept d'itérateur 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 maintenabilité dans la mesure où vous pouvez simplement changer de conteneur sans avoir à modifier quoi que ce soit. Il n'en va pas de même pour une boucle sur le fichier size d'un vecteur. Le seul autre conteneur avec lequel vous pourriez l'échanger sans devoir modifier la boucle serait un autre conteneur à 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){
  // ....
}

Plutôt long, non ? C++0x nous soulage de ce problème de longueur grâce à la fonction auto mot-clé :

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

Déjà plus beau, mais pas encore parfait. Vous appelez end à chaque itération et cela peut être mieux fait :

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

Ça a l'air bien maintenant. Quand même, plus long que l'équivalent for_each version :

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

Le terme "équivalent" étant légèrement subjectif, car l T dans la liste des paramètres de la lambda pourrait être du type verbeux comme my_type<int>::nested_type . Cependant, on peut typedef son moyen de contourner cela. Honnêtement, je ne comprends toujours pas pourquoi les lambdas n'ont pas été autorisés à ê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 indique qu'aucun élément de la séquence ne sera sauté, ce qui pourrait être le cas avec votre boucle for normale.

Cela m'amène à un autre point : Depuis que for_each est destiné à parcourir l'ensemble de la séquence et à appliquer une opération sur chaque dans le conteneur, il n'est pas conçu pour traiter les premiers return ou break en général. continue peut être simulée avec un return de l'instruction lambda/facteur.

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

En passant, for_each pourraient être "dépréciées" avec C++0x grâce aux formidables 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 les trois options suivantes :

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

0 votes

Notez que "encore, plus long que l'équivalent for_each n'est pas toujours vrai : avec la boucle for, vous pouvez utiliser la fonction auto pour la déduction de type, mais comme les expressions lambda ne sont pas polymorphes (et ne supportent pas la déduction de type), vous devez nommer le type réel dans la liste des paramètres lambda, donc T pourrait être my_long_class_type o my_class_type<unsigned long long>::nested_type et il pourrait avoir besoin d'être qualifié de const, donc en fait le code requis pour l'option for_each est souvent plus longue. Cela dit, quand c'est approprié, for_each est toujours supérieure à une boucle for.

0 votes

@James : Vous avez raison, en fait je voulais ajouter cela aussi, je ne sais pas pourquoi je ne l'ai pas fait. Édition.

0 votes

Cependant, comme le souligne Steve Jessop dans un commentaire à ma réponse, vous pouvez aussi utiliser decltype(*v.begin()) qui, bien que laid et moins lisible qu'un typename, peut néanmoins être plus court et, dans certaines circonstances, être préférable.

9voto

Billy ONeal Points 50631

Je recommande généralement l'utilisation de std::for_each . Votre exemple de boucle for ne fonctionne pas pour les conteneurs à accès non aléatoire. Vous pouvez écrire la même boucle en utilisant des itérateurs, mais c'est généralement une douleur due à l'écriture de std::SomeContainerName<SomeReallyLongUserType>::const_iterator comme le type de la variable d'itération. std::for_each vous met à l'abri de ce problème et amortit également l'appel à end automatiquement.

7 votes

Il ne peut pas utiliser auto para std::SomeContainerName<SomeReallyLongUserType>::const_iterat‌​or . (Le PO utilise déjà C++0x).

1 votes

@iammilind : Je suppose que ça marche aussi... J'aime toujours le for_each car 1. le nom spécifie ce qu'il fait réellement, et 2. la syntaxe de l'itérateur est complètement supprimée de la boucle. Mais c'est juste une préférence personnelle - les constructions sont les mêmes, donc c'est vraiment ce que l'on préfère.

8voto

iammilind Points 29275

IMHO, vous devriez essayer ces nouvelles fonctionnalités dans votre code de test .

Dans le code de production vous devez essayer les fonctions avec lesquelles vous vous sentez à l'aise. (c'est-à-dire que si vous vous sentez à l'aise avec for_each vous pouvez l'utiliser).

1 votes

+1 -- Malgré mon amour des lambdas -- si le PO n'est pas familier, il doit le devenir avant de les utiliser.

3voto

Cubbi Points 25339

for_each est le plus général des algorithmes qui itèrent sur une séquence, et donc le moins expressif. Si le but de l'itération peut être exprimé en termes de transform , accumulate , copy Je pense qu'il est préférable d'utiliser l'algorithme spécifique plutôt que l'algorithme générique. for_each .

Avec la nouvelle gamme C++0x pour (supportée dans gcc 4.6.0, essayez ça !), for_each pourrait même perdre son statut de moyen le plus générique d'appliquer une fonction à une séquence.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X