Ça dépend du rôle du break dans la boucle for.
Souvent, il s'agit de parcourir des éléments jusqu'à ce que l'un réponde à une condition/un prédicat. Dans ce cas-là, un std::find_if peut suffire.
Version imprimable
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:
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 :cry:)
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) ;)
C++11 dispose maintenant de fonctions lambda avec fermeture :
Code:
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
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?Citation:
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 :aie:
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:
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:
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:
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:
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...Citation:
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:
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):Citation:
[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...Citation:
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
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
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 ;)
Tous les algorithmes cités plus haut fonctionnent. La différence ne porte que sur leur efficacité. C'est d'ailleurs le contexte de la remarque du GotW
J'en doute.Code:[Guideline] Reuse standard library algorithms instead of handcrafting your own. It's faster, easier, AND safer!
D'abord, il ne s'agit pas d'un "si", mais d'un "quand". Les recherches sont très courantes en programmation et dès qu'un conteneur est un peu gros, l'impact en terme de vitesse est énorme (un facteur 50 à 100 pour 1000 éléments, un facteur 25 000 à 50 000 pour un million). Le problème est tellement courant que la STL fournit tous ces algorithmes. Retarder leur utilisationme semble un peu gratuit.
Par ailleurs, la refactorisation ne se limitera pas à changer un algo ou un conteneur. Dans le cas de vecteur triés, on introduit un "contrat" supplémentaire : la préservation de l'ordre. Si l'on travaille sur des structures lourdes, on va remplacer le tri par une indexation. En terme de report, c'est assez lourd, et ça risque d'introduire toutes sortes de risques d'erreurs.
Enfin, dans le cadre d'un projet réel, j'observe que les mauvais choix initiaux ont une fâcheuse tendance à perdurer. Parce que les délais sont trop courts, parce qu'il faut boucler, parce qu'il y a une nouvelle fonctionnalité qu'on considère urgente, parce qu'on ne peut risquer d'introduire une régression à ce stade, les "optimisations" dont tu parles sont souvent reportées, et on finit par livrer quelque chose d'inutilisable.
Francois
Beaucoup de nouveautés du C++11 et d'anciens algorithmes qui eux ont déjà de la bouteille, comme find, sont pour moi (même si je passe mon temps à réinventer la roue) très utile, car il permmettent de maitnenir un code concis, et toujours (voire plus) compréhensible, pour un utilisateur...lambda :mouarf:
Mais à propos de ces lambdas justement, pour moi c'est comme les foncteurs : réduction du nombre de ligne de code, mais, et beaucoup de monde va surement trouver à en dire, au détriment de la lisibilité du code. Ce que j'aprécie le plus dans un code, c'est de pouvoir le lire comme on lit un bouquin, et avec les lambdas, le but recherché n'est pas clair du tout, et je trouve ça vraiment dommage. je sais que "tout bon programmeur est un fainéant", mais je ne suis pas sur que ces nouveaux outils améliorent les performances, et je suis sur qu'il rendent le code moins compréhensible dans beauuucoup de cas. Donc je ne vois pas pour quel bon motif on doit se servir de ces bêtes là.
Les lambdas peuvent rendre un code plus lisible si on n'en abuse pas. Exemple :
Ça reste propre, compréhensible et ça m'évite de déclarer mon foncteur eLetterRemover trop loin de là où il m'est utile. Cela dit, je trouve quand même que les lambdas utilisées "en r-value" dans un for_each rendent le code hideux, comme en Java où on trouve parfois des {} dans des ().Code:
1
2
3
4
5 std::vector<std::string> container; auto eLetterRemover = [] (std::string& s) { s.erase(std::remove(s.begin(), s.end(), 'e'), s.end()); }; std::for_each(container.begin(), container.end(), eLetterRemover);
--edit--
Modifié selon la remarque de JolyLoic.
En y repensant, je trouvais bizarre qu'un STL-algorithm puisse modifier la taille de s :mouarf:
Attention : std::remove n'efface rien, c'est un piège : http://cpp.developpez.com/faq/cpp/in...ssion_elements
Ancien Delphiste passé depuis 16 mois en C++Builder 2007, je n'ai que peu l'habitude de la STL, ses conteneurs et algos
Je l'ai utilisé exceptionnellement pour la découvrir, les conteneurs comme le Map ou Set m'ont permis de faire un code élégant en STL au lieu le faire manuellement en VCL (le framework de C++Builder)
J'ai gagné du temps sur les Map, j'avais mon propre objet Delphi (à l'époque la Map n'existait vraiment en Delphi), quand j'ai vu que cela existait dans la STL, je n'ai pas hésité !
Je n'ai que vaguement notion sur les Algos STL, je suppose que la plupart sont liés aux Conteneurs STL, ces derniers ne sont pas utilisés où je travaille, les premiers encore moins :mouarf:
Pour les algos, là j'ai déjà plus de difficulté tout comme j'en ai eu au début avec la syntaxe des itérateurs !
le seul algo que j'ai utilisé fut celui de la FAQ : Comment détruire les pointeurs d'un conteneur ?
le Foncteur ajoute une indirection qui n'est pas forcément lisible, la surcharge de l'opérateur () faut être honnête, si tu n'y es pas habitué, tu peux galérer pour comprendre !
J'ai déjà un code très POO, avec un couplage le plus faible que possible avec des Interfaces, très inhabituel où je travaille, si je rajoute en plus d'autres éléments syntaxiques non pratiqués, j'aurais un code illisible pour mes collègues !
On a deux équipes, une en C++ sous Win32, l'autre en C sur micro-proc (16bits je crois), des styles radicalement différents !
Si la STL profite à la lisibilité, autant l'utiliser
Si elle amène une complexité syntaxique, j'ai déjà plus de doute (tout dépend des pratiques de l'employeur !)
Si une boucle s'annonce complexe, je l'a commente, je fais même l'inverse !
J'écrit la boucle en français, je décris ce que je vais coder
J'écrit le code réel en laissant les commentaires
Le tout compléter par un cartouche de documentation (param, return, throw ...)
La STL c'est bien quand cela rend le code plus lisible au premier coup d'oeil,
J'ai vu des algos où je travaille fortement tordu pouvant se remplacer juste par une fonction existante dans la RTL (autre partie du framework de C++Builder)
La réponse n'est pas si évidente, il faut s'adapter aussi à la société où l'on travaille, où je suis actuellement, il y a que très peu de POO, pas de séparation Présentation\Modèle (c'est même la pratique inverse).
J'ai utilisé la STL pas curiosité, je me suis même lancé dans la surcharge d'opérateur, comme () ou [] ou même << c'est très intéressant !
Mais ce code est exceptionnel, aucun code des mes autres collègues n'utilisent la STL ou la surcharge d'opérateur !
Il serait maladroit de ma part de laisser du code C++ qui ne respecte pas les standards de la société même si ils sont un peu "vieillo" !
Cela restera donc une expérimentation personnelle, que j'ai fortement documenté car méconnu !
En parallèle, j'ai proposé à mes collègues des approches POO à leur problématique, ce n'est pas parce que l'on ne pratique pas que l'on doit rester fermer, il faut évoluer petit à petit !
[HS]
Dans une ancienne société, en Delphi, un développeur PHP habitué de l'équivalent STL et des itérateurs, a fait tout plein de code avec ces pratiques, quand d'autres collègues (ou moi) ont du reprendre son code, ils se sont arrachés les cheveux car son code ne ressemblait à rien de ce qui était fait habituellement !
Maintenant, les itérateurs, les génériques existent aussi en Delphi, le for a d'ailleurs évolué, il conserve sa forme for i++ mais fourni une version for in équivalent d'un for each
L'évolution ne dénature pas les habitudes, il ressemble très fortement à un for standard contrairement aux horreurs ce fameux développeur
Encore une fois, le respect de la charte de code du langage, de l'outil et de la société doit peser dans la décision de faire code dit "old school" ou du code STL
Personnellement j'utilise les boucles while 99% du temps. Viennent ensuite les boucles do-while à 0.9% et les for à 0.1%.
J'allais écrire "Les boucles while sont hyper simples".
Non toutes les boucles sont simples mais je préfère les boucles while parce que j'aime la représentation.
C'est à dire que je préfère voire ça :
que ça :Code:
1
2
3
4
5
6
7 int LineIx=0; int LineLen=strlen(Line); while(LineIx!=LineLen) { ++LineIx; }
Je préfère les boucles while parce que dans les boucles for on a un tas d'informations collées et ça peut-être contraignant à lire pour ceux qui utilisent une police de caractères assez petite.Code:
1
2
3
4
5
6 int LineLen=strlen(Line); for(int LineIx=0; LineIx<LineLen; LineIx++) { // }
Et puis je préfère séparer les choses. Déclarer d'un côté, tester de l'autre et incrémenter à part.
Après ça dépend des personnes. Certaines peuvent lire et comprendre plus vite des lignes avec pleins d'informations tandis que certaines préfèrent le code avec plus de lignes mais de petite taille.
La seule raison pour laquelle je me mettrai au boucles for c'est si il est prouvé qu'elles s'exécutent plus rapidement.
:roll:Code:
1
2
3
4
5
6
7
8 for(int LineIx=0, LineLen = strlen(Line); LineIx < LineLen; ++LineIx) { // }
La lisibilité, ça se cherche/trouve.
Excuses-moi, je ne veux pas être désagréable/méchant mais ton bout de code est... graphiquement moche pour moi.
Lisible ? Pas plus que le tout sur une ligne.
Après c'est chacun ses goûts...
Personnellement je trouve moche un code dont les accolades ne sont pas juste en dessous de leur test conditionnel du genre :
C'est comme ça... je trouve ça moche.Code:
1
2
3
4
5
6
7 while(LineIx!=LineLen && !Abort && cChr!='\n') { ++LineIx; }
Pour moi un bon code est beau, clair, rapide et ne provoque pas d'erreurs à l'exécution.
Dans mon code les seules choses que vous pourrez trouver entre les parenthèses d'un test conditionnel c'est le test conditionnel, pas de déclaration de variables ni d'assignation.
Tout est question d'habitude. Comme quand je mets Ix après un nom de variable du genre TextIx. Ix = Index. ça signifie qu'elle me sert à accéder à un élément d'un tableau et que ce tableau s'appelle Text.
Je ne reviens pas sur les conventions d'écriture, chacun a les siennes.
Mais le for tel que je l'ai écris correspond pourtant à tes "critères"
- plus de lignes mais de plus petite taille, et donc clairement plus lisible en cas de multi-initialisation, test ou post-opération
- déclarer d'un côté, tester et incrémenter à part
C'est pourtant bien le cas. Mon for et ton while sont identiques, jusque dans leur nombre de lignes.Citation:
les accolades ne sont pas juste en dessous de leur test conditionnel
Un test ne tient pas toujours sur une seule ligne.
Avoir l'incrémentation visible en début permet de ne pas avoir à scroller sur du code parfois long pour trouver ce qu'il se passe en fin de boucle. Si toutefois il s'y passe quelque chose. En debug le temps gagné n'est pas anodin.
Et l'avantage d'un for c'est de contrôler le scope des variables utilisées, ça évite de se trimbaler une variable qui sert d'index dans tout le reste de son scope actuel.
Pour en revenir au for, une question me turlupine (mais j'ai peur de faire dévier le topic):
je m'intéresse beaucoup à l'article de Laurent Gomila sur la méta-programmation (et j'y retourne tous les jours pour essayer de comprendre une ligne de code supplémentaire :mouarf: ).
Ma question : pourquoi, alors que la méta-programmation est sans doute la méthode (la philosophie ?) de codage la plus performante, l'utilisation des algorithmes de la STL sont-ils pourtant plus répandus qu'elle ? Du coup, pourquoi ne pas bannir le for au nom de la méta-programmation plutôt ?
Dernière question : pourrait-on (moi je peux pas hein :aie:) concevoir une librairie standard qui utilise la méta-programmation (vu le principe en question j'ai des doutes, mais bon, place à vos lumières ! ) ?
:aie: Quelques erreurs :)
Alors, pour commencer, la STL utilise, quand c'est possible, la méta programmation (un exemple classique est le choix des algos a utiliser en fonction du type de conteneur)
Et la méta programmation (terme vaste et vague) n'est pas plus performante. Elle répond à une problématique différente : déterminé ce qui peut être optimisé à la compilation et ce qui doit être déterminé à l'exécution. Regarde les expressions template par exemple, tu comprendras les possibilités d'optimisation possible à la compilation.
Oui et non. Si tu veux créer un vecteur de taille dynamique à l'exécution, tu retomberas sur ce que tu as dans la STL. Si tu veux au contraire avoir des choses déterminées au moment de la compilation, là ça sera applicable. Regarde par exemple Boost.MPL, tu as des vecteurs, des for, etc. le tout déterminé à la compilation (Lis par exemple le Abhrams http://cpp.developpez.com/livres/?page=tous#L0321227255)
C'est une problématique général : savoir déterminer ce qui peut être évalué à la compilation et ce que ne peut pas l'être. La méta programmation ne fait que la première partie
Je te recommande de verifier le sens de "meta-programation"...
Ca depends ce que tu entends par la, ta phrase est tres vague.Citation:
Ma question : pourquoi, alors que la méta-programmation est sans doute la méthode (la philosophie ?) de codage la plus performante
La meta programmation, lorsqu elle est possible à la compilation, permet de laisser le compilateur générer le code executable le plus optimisé pour les différnts cas où ou utilise le code meta-programmé (si on peut dire...)
En gros, ca permet de dire généralement ce qu'on veut comme code, sans le coder, et laisser le compilateur prendre un max d'infos sur le contexte d'utilisation à la compilation pour générer le code final.
Il y a aussi la méta programmation à l'execution, comme la reflexion par exemple, mais C++ n'en fournis quasimment pas (ou très/trop peu). Les template C++ sont de la meta-programmation à la compilation, pas à l'execution. (je precise parceque meta-programmation est un terme trop vague pour decrire de quoi il est question ici).
Ces algorithms sont implémentés en tant que template, donc meta programmation à la compilation (celle dont on parle ici donc).Citation:
, l'utilisation des algorithmes de la STL sont-ils pourtant plus répandus qu'elle ?
Pourquoi ils ne sont pqs plus répandus... tu veux dire utilisés plus souvent par les utilisateurs C++ ou bien par les autres languages?
Pour le premier cas, comme souvent, la raison est historique, ou chronologique disons. Comme souligné plusieurs fois dans ce thread, ces algorithmes ont été implémenté avant qu il n y ait les lambda (qui sont extremement recents). Donc a chaque fois que tu as un paramettre qui doit être appellable comme une fonction, soit tu fournis une fonction libre (mais alors tu perds en performance parceque ça passe par un pointeur), soit un fonctor, un objet qui peut être appelé (cas otpimal).
Le problème est que définir un fonctor reviens a définir un type, puis au moins un membre operator() puis l'implémentation. Ce type ne peut pas être définis dans une fonction C++ (oui, on peut définir des class et struct en plein milieu d'une fonction C++, elle ne sera pas utilisable ailleurs) parceque ces types ne sont donc pas visible ailleurs que dans la fonctoin, et comme les fonctions de la librarie standard sont à l exterieur de notre code, et bien ils ne peuvent pas y acceder.
De plus, ecrire ces types est fastidieux, comparé a ecrire par exemple une ligne pour comparer deux objet.
Dans le nouveau C++, C++11 nous avons maintenant:
- les lambda expressions: qui techniquement ne sont qu'un racourcis syntaxique pour définir des foncteurs - mais qui du coup sont extremement pratique puisque tres courts et utilisable directement en argument de la fonction algorithme;
- la restriction des templates ne pouvant pas utiliser en pametre les types définis dqns une fonction a ete levé, ce qui fait ...qu'on peut definir un fonctor dans une fonction et l utiliser ensuite;
Du coup, aujourd'hui, il est tres facile d'utiliser ces algorithmes, alors qu'il y a disons 2 ans, c'était fastidieux, un peu long et surtout nous forcais a ecrire beaucoup de code pour dire quelque chose d esimple.
Aujourd hui c'est l'inverse. (a mon avis)
Pour les autres languages, divers choix de philosophie ont orienté la plupart des languages post-C++ vers des directions différentes concernant la métaprogrammation qui est souvent fournie à l execution au lieu d a la compilation. Cela ne permet pas du tout les mêmes choses.
Les fonctions de la librairie standard sont toutes pensées pour être a la fois génériques et exploitant le fait que le compilateur va exploiter le contexte d'appel de la fonction pour optimiser le code qu'il va générer. Sans meta programmation à la compilation, que les autres languages dont je parle n'ont pas, reproduire cela est presque impossible. Au mieu, on peut optimiser pendant l'execution, mais ca na pas du tout le meme effet.
Des languages comme D par exemple gardent cette meta programmation ala compilation et ont donc le même avantage que C++ de ce coté (et d'autres avantages d'ailleurs) mais fournis en plus la meta programmation a l execution si demandé.
Parcequ'ils n'ont rien a voir?Citation:
Du coup, pourquoi ne pas bannir le for au nom de la méta-programmation plutôt ?
Quasimment toute la librarie standard C++ est meta-programmé...Citation:
Dernière question : pourrait-on (moi je peux pas hein ) concevoir une librairie standard qui utilise la méta-programmation (vu le principe en question j'ai des doutes, mais bon, place à vos lumières ! ) ?
Ok, bon à savoir pour la STL. Pour le terme de méta-programmation, j'évoquais surtout le principe consistant à réduire au maximum les parties de code qui seront traités à l'exécution. Et si ce n'est pas seulement ça la méta-programmation, c'est en tout cas ce principe là que je considère comme ce qu'il peut y avoir de plus performant (en terme d'exécution).
dans boost.MPL il y a des vecteurs (là j'entends taille dynamique, comme pour les vecteur de la STL) qui sont déterminés à la compilation ? (désolé si je pose des questions bêtes)
EDIT :
@Klaim : Ok bon je me suis bien planté dans mes mots :aie:. J'aime pas faire des intervention foireuses :lol:
En fait je parlais juste de l'idée de réduire au max le code à déterminer à l'exécution, de l'écriture/approche assez inhabituelle :
Et je pensais que la STL n'était pas codée comme ça.Code:
1
2
3
4
5 template<unsigned int N> struct Fact { enum {Value = N * Fact<N - 1>::Value}; };
Ce que je voulais dire (mais je m'exprime mal, ça se voit :aie: ), c'est pourquoi ne pas laisser tomber les boucles for pour ce genre de code :
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 //exemple de boucle template <int Begin, int End> struct Boucle { static void Do() { DoSomething(); Boucle<Begin + 1, End>::Do(); } }; template <int N> struct Boucle<N, N> { static void Do() {} }; // Méthode habituelle for (int i = 5; i < 15; ++i) DoSomething(); // Avec notre structure Boucle Boucle<5, 15>::Do();
Tu ne peux pas parce qu'en général tu ne connais pas 5 et 15 à la compilation.
Si tout était connu à la compilation, on n'aurait pas un programme résultat, mais une pou plusieurs valeurs ;)
Bonjour,
dans certains cas surement, mais la plupart du temps, je penses notamment à l'utilisation des conteneurs ? les valeurs de retour des méthodes comme begin() et end() ne sont pas résolues avant la compilation ?
EDIT : comment on peut avoir un -1 sur une question ?
Non, ça dépend de ce qu'il y a dans le conteneur qui n'est pas connu à la compilation.
un mpl::vector est dynamique pendant le parsing du code. mais pas plus. une fois l'executable préparé... non.
et d'ailleurs il ne faut pas croire qu'une boucle déroulée s’exécutera plus rapidement qu'une boucle dynamique. la raison à cela étant le "code bloat" et des problèmes de streaming dans le cache L1. Il existe des articles de recherche très poussés sur le sujet du code bloat en C++ qui en parlent.
Si on suppose une boucle très répétée, alors le duff's device peut quand même être intéressant si on maitrise bien la taille du cache code et l'influence du déroulement sur le code.
D'ailleurs, le déroulement permet aussi des optimisations plus faciles qu'une extraction de boucle (un simple constant folding suffit, pour certains cas particuliers).