Effectivement c'est le cas le plus courant.
Mais une autre utilisation m'interpelle:
quid de réaliser une action sur un élément externe : faire une somme des éléments, remplir un autre tableau qu'on utilisera en sortie de boucle ?
Jusqu'à présent tous les exemples montrent l'utilisation de l'élément actuellement parcouru sans action sur un élément externe à lui-même.
Utiliser for_each avec une classe qui enregistre le résultat en membre sur lequel réaliser un get à la fin ?
? D'après l'exemple fourni sur http://www.cplusplus.com/reference/algorithm/for_each/
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 // for_each example #include <iostream> #include <algorithm> #include <vector> using namespace std; struct somme { myclass() : result(0) {} void operator() (int i) { result += i; } int result; }; int main () { vector<int> myvector; myvector.push_back(10); myvector.push_back(20); myvector.push_back(30); somme myobject; for_each (myvector.begin(), myvector.end(), myobject); cout << myobject.result << endl; return 0; }
Je trouve ça un peu lourd comme opération. Enfin ces "soucis" de lourdeur sont en grande partie améliorés par les lambdas (vivement que je puisse les utiliser)
Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
Un peu de programmation réseau ?
Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.
Dans ce cas-là on s'intéressera plutôt à std::accumulate avec std::plus<int>() comme BinaryOp.
A priori, ce n'est pas possible,étant donné qu'elles sont clairement prévues pour itérer sur l'ensemble de l'intervalle [begin ] end, du moins, dans celles que tu cites
De plus, mais ce n'est qu'un avis perso, je n'ai jamais aimé devoir recourir à un break ou à un continue dans une boucle, et j'ai toujours préféré, à ce moment là, utiliser une variaboe qui indique clairement la raison pour laquelle on continue (ou pour laquelle on sort de la boucle), et l'introduire dans la condition d'entrée (ou de sortie, selon le cas), quitte à changer de style de boucle, ou à placer un test qui évitera l'exécution d'une partie de code si certaines conditions ne sont pas remplies
J'avoue cependant bien volontiers que c'est fortement inspiré de la méthode que je préfère pour envisager mes algorithmes (le nassi-schneidermann, pour ne pas le citer)![]()
A méditer: La solution la plus simple est toujours la moins compliquée
Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
Compiler Gcc sous windows avec MinGW
Coder efficacement en C++ : dans les bacs le 17 février 2014
mon tout nouveau blog
C++11 dispose maintenant de fonctions lambda avec fermeture :
Code c++ : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 #include <iostream> #include <algorithm> #include <vector> #include <cstdlib> int main() { std::vector<int> myvector; myvector.push_back(10); myvector.push_back(20); myvector.push_back(30); int somme; for_each(myvector.begin(), myvector.end(), [&somme](int x) { somme += x; }); std::cout << somme << std::endl; return EXIT_SUCCESS; }
Mais de toute façon, cob59 a raison quand il parle de std::accumulate.
Ne devrait-on pas faire à ces noms expressifs le même procès qu'aux commentaires? Un nom de variable, ou de fonction, ne représente que l'idée que le programmeur qui l'a choisi s'en faisait (souvent avant d'écrire le code, alors que le commentaire est écrit après). Et si le programmeur ne met pas à jour ses commentaires, comment espérer qu'il le fasse avec ses noms de fonctions ou de variables, tâche plus délicate?
Ca nous ramène à l'expressivité des algorithmes. A moins d'utiliser des lambdas partout (ce qui n'est possible que pour de petites fonctions, et ne clarifie pas forcément le code), elle est directement fonction de la qualité du nom choisi pour la fonction qu'on leur passe.
Rien que dans la STL, les exemples de nommage trompeurs existent. Qui ne s'est pas fait prendre par le fait que remove n'enlève rien, mais "selectionne" les éléments vérifiant une condition? que search trouve l'élément recherché, alors que binary_search n'est qu'un test d'existence? que inner_product n'est pas forcément un produit, ni partial_sum une somme? Et on parle ici d'une bibilothèque standard, écrite par des pros. Que penser de nos codes en "franglais de programmeur", ou de bibliothèques écrites par des programmeurs très doués, mais dont l'anglais n'est pas forcément la matière forte?
Au final, même si, comme tout le monde, j'essaie de "nommer expressif", je me demande souvent si c'est une si bonne idée que cela. Un nom mal choisi (ou mal compris par celui qui le lira) est nettement plus dangereux, au fond, qu'on code un peu plus difficile à lire, mais qui ne pourra induire en erreur.
J'observe d'ailleurs que pas mal de textes de référence (en algorithmique notamment) proposent un code bien peu expressif...
Francois
Dernière modification par Invité ; 05/04/2012 à 22h31.
Tant que l'on est autour du parcourt dans la boucle for, quelqu'un peut-il m'expliquer l’intérêt de boost.range par rapport à ce qu'il existe déjà?
Dans l'overview :
Également si celles-ci (ceux-ci ?), apportent beaucoup? Car elles nécessitent de réécrire les aglo standard non? Vaut mieux-t-il les utiliser?The motivation for the Range concept is that there are many useful Container-like types that do not meet the full requirements of Container, and many algorithms that can be written with this reduced set of requirements. In particular, a Range does not necessarily
- own the elements that can be accessed through it,
- have copy semantics,
Merci![]()
Pour boost::range, utilises le une fois et tu verras comme c'est commode à utiliser.
Par exemple (tiré de la doc) :
Insert à la fin de vec les éléments de rng en remplacant les valeur qui respectent le prédicat par new_value et en commencant par la fin (de rng).
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 boost::push_back( vec , rng | boost::adaptors::replaced_if(pred, new_value) | boost::adaptors::reversed);
Une autre solution pour faire ca est de coder la boucle en dur (for ou for_each, peu importe), mais je doute que je soit réelement plus lisible. Ou alors d'utiliser un "Iterator Adaptator" qui rendra le for/for_each plus lisible mais sera aussi inutilement plus verbeux que boost::range (faudra écrire deux fois les "Iterator Adaptator" et ils s'echainent beaucoup moins naturellement que les "Range Adaptors").
Je pense que ca fait partie des éléments de boost dont on a très facilement envie de le réutiliser une fois qu'on l'a testé.
Pour les algos standards, ils sont déjà réécrit, donc tu n'as rien à réécrire.
PS: Je reviens pas sur le fait que le concept de Range est bien plus large que celui de Containor comme l'indique la doc de boost. Il me semble aussi que tu dois pouvoir trouver des message de Alexandrescu expliquant la force des range par rapport aux itérateurs.
C'est marrant tout de même.
Les anti-algo-stl de ce post semblent avoir oublié un détail énorme.
La boucle for permet d'itérer dans un ordre précis une somme d'élément.
Cette boucle for classique est donc le pendant de for_each.
Enfin, pas tout à fait. En effet, for_each ne peut pas modifier la collection elle-même: uniquement ses éléments.
Si l'ordre n'a aucune importance, genre charger N plug-ins dans un système simple (ou il n'y à pas de dépendances inter-plugin en fait), utiliser transform prend son intérêt.
Parce que, selon la norme, transform ne garantit pas l'ordre de traitement. Et le code exécuté par transform ne doit pas avoir d'effets de bord (c'est transform qui a ces effets de bord, par le retour de la fonction qu'il exécute).
Bon, c'est sûr, il faut s'intéresser à la doc. Mais quand on me parle de C#, je dois souvent reprendre la personne avec un truc du genre "Tu parles pas de C# la, mais de .NET.". Pour JAVA, c'est la même. Les gens d'aujourd'hui (en tout cas ceux avec qui je peux discuter) confondent langage et framework, et ce qui fait la réputation du manque de productivité de C++ est son framework standard limité. Alors qu'il s'agit du langage le moins limité j'ai l'impression. En C++, Qt (par exemple) refait tellement de mécanismes qu'il s'agit presque d'un langage différent (d'ailleurs, ils vont jusqu'a modifier la chaîne de compilation avec leur moc. Un peu comme le faisait CFront.)
C++11 corrige justement une partie de ce problème, tout en conservant la puissance ancestrale qu'il à héritée du C (et l'absence d'interface graphique ^^).
FOR ne dois pas disparaître, on en a besoin pour implémenter certains algos. Et GOTO ne doit pas disparaître non plus, on ne sait jamais, peut-être que certains s'en servent. N'empêche, 90% des boucles for classiques peut sûrement sauter, en faveur du "for(auto i:truc)" (qui, d'ailleurs, EST UN for spécialisé pour traiter la majorité des cas).
Et dans ces 90% un certain nombre pourrait être remplacé par les algos.
Honnêtement, les algos, je galère à m'en servir dans des cas qui sortent des cas d'école quand même.
Par exemple, une fonction à 2 arguments. On peut la réduire avec des bind1st/bind2nd, c'est vrai (je ne connaît pas encore le bind tout court). Sauf que je n'arrive pas à utiliser ces binders avec les lambda (je suis en train de découvrir ce type de constructions, je dois rater un truc)?
Et si le d'aventure le conteneur contient des unique_ptr, et qu'on cumule ces 2 problèmes, alors même avec une fonction écrite séparément, je n'y arrive pas. Du coup les algos sont certes plus utilisables qu'en C++03, mais ne restent pas aisés à manipuler pour moi.
Pour en revenir à 2-3 trucs lus:
Je pense que la méthode GenerateSomething est probablement juste mal conçue. Si elle prenait juste l'élément à modifier, avec les algo, ça donnerait ceci:
A noter, d'ailleurs, qu'il serait possible d'ajouter l'attribut const au paramètre, et ainsi permettre d'assurer que la donnée du 1er tableau ne serait pas modifiée par la fonction. Conséquence, ça pourrait ainsi permettre de remplir un autre tableau avec le résultat, avec la garantie d'une source inviolée.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 Elem GenerateSomething(Elem &elem); foo() { transform(myArray.begin(),myArray.end(),myArray.begin(),&GenerateSomething); }
Et ce code, si transform est codé pour être utilisé en thread, sera même plus rapide.
2nd:
En fait, souvent, le break sert à identifier à quel élément on s'arrête. Ici, il est vrai que la solution STL que je vois (je ne suis pas du tout un expert, je galère comme un fou dès qu'il faut utiliser plus d'un argument, surtout avec les lambda, j'ai pas encore bien pigé la syntaxe) consomme plus de cycles processeurs dans le cas de conteneurs non triés, mais elle à l'avantage de permettre une meilleure atomicité des opérations dans certains cas (si l'ordre d'exécution n'est pas important, la majorité de mes boucles for, perso) :
Bon, naturellement, on peut n'utiliser qu'un seul itérateur de limite (plus courant je pense).
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 auto it_first=find_if(array.begin(),array.end(),predicateFirst); auto it_last=find_if(it_first,array.end(),predicateLast); for_each(it_first,it_last, someWork);
Toujours évidemment, dans certaines situations, il y a au moins une partie de boucle supplémentaire inutile (entre it_first et array.end() en fait).
La situation en question est celle ou un conteneur non trié est utilisé. Dans l'autre cas, la complexité varie selon l'endroit ou se situe le dernier élément. Du coup, on peut avoir une optimisation en nombre de cycles.
Il y a aussi le cas de l'utilisation de transform, qui peut permettre au compilateur d'optimiser, selon ce que fait someWork, pour le multi-thread.
En fait, dans les cas ou au moins une de ces choses est vérifiées:
_ un utilise une liste non triée
_ l'ordre des opération est important
_ someWork a des effets de bord
Il est probablement plus efficace de coder dans la boucle même le "break".
A noter que dans les cas ou l'on veut juste sauter un élément non valide (par exemple), on peut toujours utiliser ceci:
avec someWork qui fait un vulgaire "if(do_I_continue)return;"
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2 for_each(array.begin(),array.end(),someWork);
Je n'ai pas compris pourquoi tu veux utiliser les bind avec des lambda? La capture du lambda fait le boulot pour toi, bind est obsolete...Par exemple, une fonction à 2 arguments. On peut la réduire avec des bind1st/bind2nd, c'est vrai (je ne connaît pas encore le bind tout court). Sauf que je n'arrive pas à utiliser ces binders avec les lambda (je suis en train de découvrir ce type de constructions, je dois rater un truc)?
Code non testé mais c'est pour vérifier si j'ai bien compris de quoi tu parles?
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 std::function< void () > do_it_later( std::function<void ( int, const std::string& ) func, int i, const std::string& s ) { return [=] // capture toutes les variables utilisées dans la lambda par copie // liste d'arguments optionelle, implicitement vide: () { f( i, s ); } // appelle donc les copies d i et s qui ont les valeurs de ces variables au moment de la creation de cette lambda ; } void do_something( std::function<void ( int, const std::string& ) func ) { auto todo = do_it_later( func, 4, "Hello World" ); // do something else todo(); // call func( 4, "Hello World" ); }
quel est fondamentalement la différence entre utiliser for_each et une boucle for ? il n'y en a pas, c'est une question de goût. Tout ceci ressemble à de la masturbation intellectuelle.
Il y en a, lis la discussion.
Les GotW en parlaient déjà en 1997 dès l'issue #3.
La conclusion (pour ceux qui ont la flemme de le lire):[Guideline] Reuse standard library algorithms instead of handcrafting your own. It's faster, easier, AND safer!
Cela dit, sans lambda il est raisonnable de considérer l'utilisation de certains de ces algorithms comme fastidieux a utiliser.
Maintenant, si on a accès à un compilateur récent, il n'y a plus vraiment d'autre excuse que l'ignorance de l'existance des algorithmes pour ne pas les utiliser lorsqu'ils sont l'outil adéquate (c a d pas quand on a une boucle particulière à écrire, en suivant les prioriétés de choix recommandés dans cette discussion).
Les GotW parlent de find(), ce qui est assez différent, je crois, de l'idée proposée de supprimer les boucles en les remplaçant par des algorithmes appelant des foncteurs (ou des lambdas, ce qui revient exactement au même : un lambda c'est juste un "foncteur local"). Dans le premier cas, l'algorithme fait exactement le travail demandé, ne pas l'utiliser est absurde (même si le risque encouru en écrivant sa boucle à la main n'est pas bien grand...).
Dans le second, on remplace une syntaxe classique par une de plus haut niveau, mais il reste toute la logique à écrire. Il y a matière à débat (et je suppose que c'est pour cela que ce fil est un "débat" et pas une entrée de FAQ)
Ceci dit, dans le cas de find(), la "guideline" me parait très mauvaise. find() est un algorithme linéaire, il sera inefficace sur tout conteneur trié (cas très fréquent). Et le problème sera plus visible avec une boucle codée à la main qu'avec find(), qui cache son implémentation. Ca nous ramène au débat : utiliser un algorithme "parce que" c'est un algorithme, c'est presque toujours une mauvaise idée.
Quant au GotW 2 auquel il est fait référence, j'avoue avoir souri en lisant:
Ben tiens! A croire que les compilateurs optimisants n'existaient pas en 97...3. This one was more subtle. Preincrement is more efficient than postincrement, because for postincrement the object must increment itself and then return a temporary containing its old value. Note that this is true even for builtins like int!
Francois
Dernière modification par Invité ; 08/04/2012 à 15h08.
J'utilise souvent find avec des vecteurs d'objets non triés. Je n'ai jamais eu d'experiences où c'était fréquent...
Donc je ne suis pas d'accord avec le fait que find soit inefficace, c'est exactement l'outil nécessaire pour pas mal de choses, même si ce n'est pas les cas les plus fréquents pour toi, ça l'est pour quelqu'un d'autre.
Même chose pour les algos relatifs aux heap, que j'utilise rarement mais qui sont beaucoup utilisés dans certains domaines assez généralistes.
Je ne dis pas le contraire. find() est une recherche linéaire, elle sera optimale partout où la recherche linéaire est optimale, et inefficace partout ailleurs. C'est exactement ce qui m'ennuie avec la remarque du GotW, car elle peut laisser penser (et on observe dans des commentaires lus sur ce forum et ailleurs, comme dans du code réel, que certains le pensent effectivement) qu'un algorithme sera "toujours" plus efficace qu'un code fait main.
Un exemple très clair est celui des tris. sort() est un dérivé de quicksort, il a ses conditions d'application, qui sont très larges, et certains cas où il n'est pas efficace (par exemple, celui d'un grand tableau prenant un petit nombre de valeurs, il faut alors un bucket sort, ou alors d'un tableau presque trié, ou un tri par insertion gagne toujours).
Sur le caractère plus ou moins fréquent des conteneurs triés, c'est à mon avis un choix de conception. J'ai très souvent des conteneurs triés, parce que je constate que dès qu'on cherche plusieurs fois dans un conteneur, il devient intéressant de le trier. Un tri étant O(n log n) et une recherche linéaire N/2 si fructueuse, N si infructueuse, il est rentable de trier dès qu'on a plus d'une dizaine de recherches à faire. Ceci reste vrai, d'ailleurs, si on ajoute de temps en temps des éléments au conteneur (à condition de ne pas utiliser sort() pour le retrier).
Francois
Dernière modification par Invité ; 08/04/2012 à 16h01.
Pour le std::for_each, l'unique avantage est d'éviter d'oublier de stocker l'itérateur de fin (et d'éviter de nombreux appels à end()).
Je t'accorde que c'est assez limité.
Pour la guideline elle reste valable:
- si tu as un vecteur trié, tu vas utiliser un std::lower_bound pour trouver l'élément cherché (à moins que tu ne recodes à chaque fois une recherche logarithmique)
- si tu as une map ou un set, tu vas utiliser la méthode de classe fournie par la STL.
Dans les deux cas, tu n'as pas besoin de réécrire quoi que ce soit.
Oui, en fin de compte, on va utiliser find() pour un vecteur non trié ou une liste, lower_bound() pour un vecteur trié, une fonction membre pour les map, set et conteneurs hachés, et... je ne suis pas certain quoi, pour les multimap et multiset. Ceci veut dire que si l'implémentation change (ou le fait qu'un conteneur non trié le devienne), il faudra changer l'algorithme. En pratique, ca veut surtout dire que pas mal de programmeurs utiliseront find(), et s'étonneront de la lenteur de leur code.
C'est exactement ce qui me fait dire que find() est peut être le plus mauvais exemple qu'on puisse prendre pour dire qu'il faut préférer les algorithmes.
Francois
Humm... Je ne suis pas forcément sur...
Bien avant d'avoir un code rapide, la priorité est au fait d'avoir un code qui fonctionne.
De ce point de vue, find est l'exemple même de code qui fonctionnera quel que soit le conteneur, et qui permettra de garder quelque chose "qui marche" tant que le conteneur précis dans lequel se trouveront les données ne sera pas fixé
si, par la suite, il s'avère que, pour une raison ou une autre, nous obtenons de mauvais temps d'exécution, il sera beaucoup plus facile de changer de conteneur (et de fonction de recherche) si on doit rechercher "find(tab.begin(),tab.end() " que si on doit rechercher toutes les boucles que l'on a pu créer dans l'ensemble des fonctions qui seront impactées par le changement de conteneur
Quelque part, on gagne donc sur les deux tableaux à utiliser find au lieu d'une boucle classique : on obtient directement quelque chose qui fonctionne ET on se donne l'occasion de changer d'avis en cas de besoin![]()
A méditer: La solution la plus simple est toujours la moins compliquée
Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
Compiler Gcc sous windows avec MinGW
Coder efficacement en C++ : dans les bacs le 17 février 2014
mon tout nouveau blog
Partager