Coder efficacement, bonnes pratiques et erreurs à éviter

Posté par  . Édité par BAud, Nÿco, Thom, Benoît Sibaud et olivierweb. Modéré par Ontologia. Licence CC By‑SA.
30
14
avr.
2014
C et C++

Développer une application peut rapidement devenir un enfer si vous n'avez pas anticipé certaines difficultés et fait les bons choix au moment de sa conception. Ce livre vous aidera à vous poser les bonnes questions et à tirer le meilleur parti de la programmation objet, en particulier en C++, sans tomber dans les pièges les plus fréquents. Il clarifie certains concepts délicats et passe en revue un ensemble de bonnes pratiques, qui rendront vos projets plus évolutifs et plus faciles à maintenir.

Titre de l'image

Organisé en quatre parties, ce livre traite successivement des principes de base, des fondements de la programmation orientée objet et des spécificités du C++ pour finir sur une étude de cas complète (création d'un jeu d'échecs en 3000 lignes) qui montre comment les mettre en œuvre dans la pratique.

L'auteur insiste sur la nécessité d'anticiper les évolutions sans tomber dans l'écueil de l'over-engineering, il met en relief des erreurs courantes comme le fait de tester, d'une manière ou d'une autre, le type dynamique (réel) d'un des objets qu'on ne connaît que sous la forme de leur type de base pour décider de l'utilisation qu'on en fera. Il fournit ensuite un exemple illustrant le risque encouru. Plus spécifiquement sur le C++, il invite à renoncer définitivement à certaines habitudes du C totalement injustifiées en C++, il clarifie l'emploi des pointeurs et des références, il encourage à recourir à la constance, il montre comment tirer parti de la forme canonique de Coplien (constructeur par défaut, constructeur par copie, operateur d'affectation et destructeur virtuel ou non), etc. L'étude de cas vise avant tout à illustrer plus concrètement comment mettre en pratique, avec pragmatisme, ses recommandations.

Ce livre n'est pas un manuel d'apprentissage du C++. Un autre ouvrage du même auteur dédié au C++14 est en cours de rédaction.

Tous les exemples sont en C++11. En présentation, la situation où l'on souhaite une valeur numérique constante. Il y a plusieurs manières de les déclarer.

  • À l'aide du préprocesseur. Mais il n'y a aucun contrôle sur le type de données.
#define FIRST 1
#define SECOND 2
#define LAST 0xFF
  • À l'aide de constantes statiques. Cette méthode corrige le problème du type de données. Néanmoins, le fait d'énumérer correctement tous les cas dans un test reste le problème du programmeur.
static const unsigned int first = 1;
static const unsigned int second = 2;
static const unsigned int last = 0xFF;
  • Le cas d'une énumération, en bonne et due forme, règle pas mal de ces problèmes.
enum MyEnum{
      first = 1;
      second = 2;
      last = 0xFF
}

Il s'en suit alors quelques pages présentant les nouveautés et les bons usages du C++11 sur les énumérations.

L'auteur Philippe Dunski est expert en C++/Qt/boost et il intervient régulièrement sur developpez.com pour aider les autres membres. Il écrit de manière très personnelle et vivante, ce qui rend la lecture du livre très agréable, même sur les aspects les plus pointus.

Aller plus loin

  • # Ca a l'air intéressant comme bouquin

    Posté par  . Évalué à 5. Dernière modification le 14 avril 2014 à 22:10.

    Ce livre ne se rapprocherait-il pas du même genre que "Coder proprement" ("Clean Code" en VO) qui est orienté plutôt Java ?

    • [^] # Re: Ca a l'air intéressant comme bouquin

      Posté par  . Évalué à 3.

      Sans avoir lu celui-ci, il annonce au moins la couleur en précisant "en C++", contrairement a "Coder proprement" qui est "plutôt beaucoup" orienté java alors que rien ne l'indique explicitement

    • [^] # Re: Ca a l'air intéressant comme bouquin

      Posté par  (site web personnel) . Évalué à 4.

      Clean Code n'est pas orienté java. C'est applicable à n'importe quelle langage, avec un net tropisme autour des langages objets néanmoins.

    • [^] # Re: Ca a l'air intéressant comme bouquin

      Posté par  . Évalué à 4.

      Franchement, j'ai un peu de mal avec la notion de coder proprement sans faire référence au langage. À part des généralités triviales (commenter son code, donner des noms de variables signifiants, nommer les constantes, etc) qui prennent 2 ou 3 pages à énoncer, on rentre forcément dans les bonnes pratiques du langage. Comment parler des constantes sans parler de #define ou des enum? Comment définir un bon découpage des fichiers sans faire référence au langage? Je pense qu'un bouquin qui ne fait pas référence au langage sur sa couverture a de fortes chances d'être totalement inutile : soit trompeur (il porte sur un seul langage, avec très peu de chances que ça corresponde aux besoins du lecteur), soit trivial.

      • [^] # Re: Ca a l'air intéressant comme bouquin

        Posté par  . Évalué à 9.

        « Design Patterns – Elements of Reusable Object-Oriented Software » fait environ 500 pages, il est utile pour n'importe quel langage orienté objet à classe (il est moins utile pour les langages à typage dynamiques comme python ou ruby c'est vrai). Il est utile pour n'importe quel programme C++, C#, Java, Objective-C, probablement ADA récent et pour un tas d'autres langages.

        On doit pouvoir sortir un livre en retirant les recettes du livre « The Art of Computer Programming ».

        Même si se baser sur un langage est pratique et permet d'aller plus en profondeur, il y a tout de même un paquet de choses qui sont assez génériques.

        Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

        • [^] # Re: Ca a l'air intéressant comme bouquin

          Posté par  . Évalué à 3.

          Bah, on ne parle pas de la même chose. Les designs patterns sont des abstractions de la structure d'un programme OO, le bouquin n'a pas la prétention d'expliquer comment on code proprement. Il s'agit de choses qu'on peut traiter en pseudocode par exemple, ça serait la même chose avec un bouquin sur la programmation fonctionnelle ou les algorithmes de tri…

          Par ailleurs, la programmation object n'est pas un truc monolithique. La plupart des langages implémentent au moins des pseudo-classes (voire de simples listes) qui peuvent être utilisées pour mimer de la programmation objet, mais certains patterns nécessitent par exemple de l'héritage multiple ou des classes virtuelles, et là, on tombe tout de suite sur une poignée de langages.

          Pour revenir au sujet du bouquin, les pires conseils que je n'ai jamais lus sur comment écrire du code propre venait de gens qui appliquaient des recettes qu'ils connaissaient sur leur langage de prédilection sur un autre langage: indenter du C++ comme on le fait en python, mettre une fonction par fichier en perl, terminer les lignes par un point-virgule alors que c'est optionnel dans le langage, etc.

          • [^] # Re: Ca a l'air intéressant comme bouquin

            Posté par  (site web personnel) . Évalué à 4.

            indenter du C++ comme on le fait en python

            Kerzut, c'est mon cas. Sauf que j'ai commencé à faire comme cela en codant en C/C++, avant même de connaître Python, en mettant les accolades au niveau du bloc qu'elles encadrent - la recherche de l'accolade correspondante et la visualisation du bloc de code concerné sont immédiats.

            void truc(int x, int y)
              {
              
              }

            Python 3 - Apprendre à programmer dans l'écosystème Python → https://www.dunod.com/EAN/9782100809141

            • [^] # Re: Ca a l'air intéressant comme bouquin

              Posté par  . Évalué à 2.

              Mouais, c'est difficile de dire que c'est le mal absolu, mais le principe des conventions, en général, c'est que ça simplifie la vie de tout le monde quand elles sont respectées. Si dans ta fonction x c'est l'ordonnée et y c'est l'abscisse, il n'y a pas vraiment de faute de programmation, mais ça pourrit quand même la vie des autres.

              • [^] # Re: Ca a l'air intéressant comme bouquin

                Posté par  (site web personnel) . Évalué à 2.

                Tu indenterais comment les déclarations de paramètres ?

                (note: si je tombe sur un code d'un projet qui a déjà défini ses conventions, il va de soit que je les reprend - mais pour un code privé perso…)

                Python 3 - Apprendre à programmer dans l'écosystème Python → https://www.dunod.com/EAN/9782100809141

            • [^] # Re: Ca a l'air intéressant comme bouquin

              Posté par  (site web personnel) . Évalué à 2.

              C'est le style Whitesmiths. J'ai un mal de chien avec ce truc sur les projets qui l'exigent. J'oscille entre le style Allman et K&R/Stroustrup quand j'ai le choix.

              Plus d'infos là: http://en.wikipedia.org/wiki/Indent_style

      • [^] # Re: Ca a l'air intéressant comme bouquin

        Posté par  (site web personnel) . Évalué à 6.

        Il ne faut pas se leurrer, il y a beaucoup de concepts qui transcendent les langages. Toute la partie OO sur SOLID et Déméter est applicable à tous les langages OO mainstream. Et ce n'est pas parce que c'est indépendant du langage qu'il ne faut pas les traiter!

        Après, il ne faut pas s'inquiéter, le livre est définitivement orienté C++. Quand Philippe/koala aborde des principes comme l'ISP, il le fait selon un angle C++.

        (Et vous pouvez rajouter toutes les spécificités du C++ en plus: RAII, valeur vs entité, …)

      • [^] # Re: Ca a l'air intéressant comme bouquin

        Posté par  (site web personnel) . Évalué à 4.

        Je pense qu'un bouquin qui ne fait pas référence au langage sur sa couverture a de fortes chances d'être totalement inutile : soit trompeur (il porte sur un seul langage, avec très peu de chances que ça corresponde aux besoins du lecteur), soit trivial.

        Et ben je ne pense pas.
        Par exemple un bouquin que j'ai vraiment apprécié est Tout sur le code (ou Code complete pour la version anglaise). Ce bouquin ne s'adresse pas à un langage précisément mais utilise des exemples dans plusieurs langages pour illustrer ses propos. Et le résultat est plutôt intéressant.
        C'est justement un bouquin que j'aime bien parce qu'il ne cible pas un langage. Ce qui fait qu'il peut être lu par n'importe quel développeur. Et il ne tombe pas dans le côté trivial.

  • # C'est pourtant évident.

    Posté par  . Évalué à 5.

    La liste des erreurs à éviter tient en une ligne :

    • coder en C++.
    • [^] # Re: C'est pourtant évident.

      Posté par  . Évalué à 2.

      Posté par haleth le 08/03/14 à 14:25.
      En réponse à la dépêche Les femmes dans l'informatique.
      Évalué à -2 (+30/-28)

      Record battu !!

      http://linuxfr.org/nodes/101384/comments/1525023

    • [^] # Re: C'est pourtant évident.

      Posté par  . Évalué à 0.

      En fait le nouvel affichage est beaucoup plus utile pour detecter les trolls. Un moyen pour filtrer les commentaires en prenant compte le fait qu'il y a beaucoup de votes en plus de la note, ça peut permettre aux gens qui ont envie de filtrer plus simplement le contenu.

      • [^] # Re: C'est pourtant évident.

        Posté par  . Évalué à 1.

        Ce n'est pas tant le nombre que l'équilibre entre « pertinent » et « inutile » quelque chose du genre :

        Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

    • [^] # Re: C'est pourtant évident.

      Posté par  (site web personnel) . Évalué à -5. Dernière modification le 17 avril 2014 à 11:02.

      100% d'accord. Le C++ est un langage trop complexe et dangereux, il ne devrait certainement pas être utilisé dans des codes ou la fiabilité est critique. Malheureusement c'est encore le cas!

      Un bon langage ne devrait pas avoir besoin de ce type de livre pour éviter les erreurs. Elles devraient être éviter autant que possible lors de la conception. Evidemment c'est plus facile à dire qu'à faire et même si c'est un but il est difficilement atteignable. Il a cependant différents langages sur le marché et il faut bien avouer que le C++ n'est pas dans le top des langages fiables, compréhensibles et maintenables.

      • [^] # Re: C'est pourtant évident.

        Posté par  (site web personnel) . Évalué à 4.

        Ce type de bouquin est loin de traiter des choses qui sont spécifiques au C++. C'est d'ailleurs le sens que j'avais voulu donner dans la préface : dans les difficultés du C++, il y a celles du génie logiciel, celles du monde OO, celles du métier à traduire, etc. Plus celles spécifiques au C++.

        Et ces dernières sont des difficultés essentiellement parce que les développeurs sont restés avec leur idées préconçues d'un C avec classes où la mémoire se gère à la main, et où les erreurs se remontent à la main. C'est sûr que ça ne scale pas et que ce type de programmation ne peut que nous exploser à la figure. Je traduis cela comme un problème de formation. D'où ce type de livre (celui dont on parle, mais aussi les XC++ de Sutter, le C++ Coding Standards de Sutter & Alexandrescu, etc. qui vont tous traiter du RAII).

        Et BTW, j'attends toujours de voir le langage maintenable (et donc à la philosophie compréhensible par tout le monde, ce qui tend à exclure les langages fonctionnels qui ne sont pas dans les moeurs), sécurisé, et tout et tout.

        • [^] # Re: C'est pourtant évident.

          Posté par  (site web personnel) . Évalué à 3.

          Pour les langages, rien de parfait mais je trouve qu'Ada va dans le bon sens et aide énormément à la construction de projets larges et fiables. J'ai aussi assez été bluffé par le langage Eiffel mais qui malheureusement semble s'évanouir progressivement.

        • [^] # Re: C'est pourtant évident.

          Posté par  (site web personnel) . Évalué à 7. Dernière modification le 17 avril 2014 à 13:52.

          Et BTW, j'attends toujours de voir le langage maintenable (et donc à la philosophie compréhensible par tout le monde, ce qui tend à exclure les langages fonctionnels qui ne sont pas dans les moeurs), sécurisé, et tout et tout.

          Ben forcément si tu refuses la réponse par principe…

          La programmation fonctionnel à l’immense avantage :
          — d’avoir un système de type excellent : en Caml ou Haskell, si ça compile c’est qu’à priori il n’y a pas de bug (évidemment il peut rester des bugs, mais le gros des bugs ne passe pas la compilation) ; par contre erlang est moyen de ce côté là
          — d’avoir une gestion de la mémoire facile et locale : la seule subtilité c’est la récursion terminale, mais c’est pas non plus compliqué à comprendre et ça ne concerne à chaque fois qu’une fonction. Locale parce que si aucune fonction n’as de fuite mémoire (comprendre récursion non terminal) tu sais que ton code entier n’as pas de fuite.
          — gère le multi threading de manière naturelle (cf Erlang)

          Je ne dis pas que c’est l’alpha et l’omega et que tout est parfait. Mais si tu veux un langages maintenable la solution consiste peut-être à changer de paradigme ! C’est justement la raison qui à pousser Erlang à ne pas être objet ; cf un article du fondateur du langage : http://harmful.cat-v.org/software/OO_programming/why_oo_sucks

          Je programme trop peu pour dire que l’objet pue et que le fonctionnel déchire ; mais si tu ne trouve aucun langage objet/impératif maintenable, peut-être que le problème c’est l’objet/impératif et c’est idiot de refuser par principe d’autre paradigme.

          • [^] # Re: C'est pourtant évident.

            Posté par  (site web personnel) . Évalué à 2.

            Je réfute le fonctionnel en production car la logique de pensée n'est pas compatible avec l'immense majorité des développeurs sur le marché. Ce n'est pas "par principe". Loin de là.

            Je reconnais volontiers les qualités des langages que tu cites, mais fait voir les réactions quand on commence à suggérer OCaml en place de Python (par exemple).

            Parenthèse. Sur les critiques de l'objet, Stepanov avait donné une conférence très intéressante où il parlait, entre autres, du pourquoi la STL (une des grosses parties de la bibliothèque standard du C++) n'est pas OO. (là, à priori: http://video.google.com/videoplay?docid=-7867514878945314586)

            • [^] # Re: C'est pourtant évident.

              Posté par  (site web personnel) . Évalué à 3.

              Je comprend ton point de vu mais je trouve dommage que l’immense majorité des développeurs ne peuvent pas apprendre un nouveau paradigme. D’autant plus si celui ci permet d’écrire des programme plus concis, moins buggés, d’avantage maintenable…

              Pour ta vidéo, tu aurais un lien sans erreur 404, ça m’intéresse.

              • [^] # Re: C'est pourtant évident.

                Posté par  (site web personnel) . Évalué à 1.

                Désolé. Avec les proxys je ne pouvais pas vérifier. Il ne me reste que le titre: Donc, une conf d'Alexander Stepanov: "STL and Its Design Principles" (2002)

              • [^] # Re: C'est pourtant évident.

                Posté par  . Évalué à 10. Dernière modification le 20 avril 2014 à 20:12.

                Ça me rappelle un entretien accordé par Brian Kernighan :

                M: I have a question about research in language design. It's interesting for instance that Java is very much hyped and the community is split among the merits and flaws of the language. The language has indeed acquired some nice features proposed by researchers in the area (like garbage collection), but also the researchers point some of its weaknesses (like the arrays which are covariant and they shouldn't be). There's a whole body of research done in programming languages nowadays, and a very interesting body of research in functional programming languages, but you don't see this research to really influence the real world, i.e. what people are really using on an everyday basis. Instead all sorts of ad-hoc languages pop up like Perl or Python or stuff like that. Where do you see the fault; what's not right?

                K: That is unfortunately a very good question, and there's a certain amount of discussion here at Bell Labs between a very strong group in functional programming languages and a group using very much ad-hoc, pragmatic languages. I honestly don't know why the functional languages don't succeed. For instance ML, which is arguably the best combination, perhaps the one that ought to succeed: in spite of being a very well designed language, thought hard about by a lot of good people over a very long time, embodying an enormous amount of effort of compiler technology, still does not seem to be broadly used. I will oversimplify a lot, and probably offend my friends, by saying that the only thing people do with ML is to make ML compilers. [laughing] I'm overstating intentionally, but it has some of that flavor, and I don't really understand why. I think, speaking only for myself, part of the reason that ML in particular, and functional programming languages in general have not caught on more broadly, is that they're aimed at people who have mathematical sophistication, who are able to think in more abstract ways, that lots of other folks, and I include myself, have trouble with. Whereas languages like C are very operational, you can see how every single piece of them maps into what's going on in the machine in a very very direct sense. If I had been brought up at a different time and in a different environment perhaps I'd be totally comfortable in ML and would find C unsafe, a little dangerous, not very expressive. But my sense is that the functional languages come out of a fairly mathematical community and require a fairly mathematical line of reasoning and therefore are difficult for the people on the street.

                M: So I guess, the suggestion is for the researchers to somehow lower the language level, to promote the good qualities?

                K: I didn't really answer the other part of your question, why research in languages has not had as much effect as it should have. I think actually it has had an effect, in places like parser technology, code generation, no matter what the language is: research had a big effect on building language tools but less on the design of languages per se.

                The languages that succeed are very pragmatic, and are very often fairly dirty because they try to solve real problems. C++ is a great example of a language that in many ways has serious flaws. One of the flaws is that it tried very hard to be compatible with C: compatible at the object level, compatible very closely at the source level. Because of this there are places where there's something ugly in the language, weird syntactic problems, strange semantic behaviors. In one sense this is bad, and nobody should ever do that, but one of the reasons that C++ succeeded was precisely that it was compatible with C, it was able to use the C libraries, it was usable by the base of existing C programmers, and therefore people could back into it and use it fairly effectively without having to buy into a whole new way of doing business. And this is not the case for ML, which was being done at about the same time and, at least partly, in almost the same place, but which took a very different view of the world. As a pragmatic thing, C++ is extremely successful but it paid a certain price by being compatible with the previous language.

                Personnellement, j'ai naguère essayé de me mettre à Ocaml, malheureusement tous les tutos que j'ai alors trouvés étaient effectivement très orientés "matheux" ; le genre plein de formules incompréhensibles pour moi et se paluchant longuement sur les suites de Fibonacci, sans jamais expliquer comment simplement récupérer/reverser les données dans un fichier (le B-A-BA du truc utile, quoi).

            • [^] # Re: C'est pourtant évident.

              Posté par  . Évalué à 4.

              J'ai des amies qui font des études de langues, et qu'on n'ont aucune formation mathématique ni informatormatique derrière. Il semble bien que ocaml ne soit pas beaucoup plus compliqué à apprendre que java ou python (il y a les 3 dans la licence).

              Je pense que ton point de vu est biaisé. Parce qu'un langage fonctionnel est compliqué à apprendre pour quelqu'un qui a été formé à pensé avec des des langages objets et impératifs, tu en déduis qu'il est nécessairement plus compliqué pour des néophytes totals d'apprendre les langages fonctionnels. Hors, justement, ça ne l'est pas. Tu réproduits des schémas qui s'appliquent à ton cas (enfin, ne prends rien personnellement). Hors, dans le cas ou tu maitrises déjà C++/autre, il ne te faut pas simplement apprendre, il te faut aussi et surtout désapprendre avant de pouvoir ré-apprendre. Ce qui induit une complexité qui n'est pas du tout présente pour les gens qui n'ont rien à désapprendre du tout.

              j'ai moi même commencé avec un langage fonctionnel et je trouve au contraire extremement compliqué d'apprendre des langages objets/impératifs qui dépassent la simplicité du C. (simplicité au sens des concepts mis en jeux).

              • [^] # Re: C'est pourtant évident.

                Posté par  . Évalué à 3.

                J'ai regardé haskell suite à la dépêche. J'ai lu l'intégralité du tuto en lien à la dépêche. Si j'ai compris certains trucs, il me manque de l'expérience. J'ai commencé le BASIC-A en 1986, puis Turbo-Pascal, ASM, … Aujourd'hui, je pense clairement algorithme impératif.
                Je me souviens du Prolog à la FAC et en stage, où après avoir réappris à penser, c'était facile. Mais dans le milieu professionnel, je n'en ai jamais refait.
                C'est une approche différente. As-tu des TP avec des exercices à essayer qui sont adapté à la programmation fonctionnelle ? car je crois que c'est ce qui me manque le plus pour le moment.

                • [^] # Re: C'est pourtant évident.

                  Posté par  . Évalué à 2.

                  Ça demande réfléxion, mais le seul truc qui me vient à l'esprit dans l'instant c'est cet article :

                  http://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf

                  Il ne s'agit pas d'exercices mais je trouve qu'il est très bon pour ce qui est d'expliquer les points centraux de la programmation fonctionnelle.

                  • [^] # Re: C'est pourtant évident.

                    Posté par  (site web personnel) . Évalué à 4.

                    J'ai beaucoup aimé le livre le langage caml qui est une introduction à la programmation et caml. La partie caml est aujourd'hui dépassée, mais les exemples donnés (tour de hanoi, tri rapide) sont très pragmatiques. (J'ai trouvé aussi très amusant l'aspect récursif du livre, mais il se peut que ça ne fasse rire que moi…)

                    Beaucoup plus théorique, il y a le typeclassopedia que l'on peut aussi trouver en pdf qui fait le tour sur la notion de foncteur. L'article ne fait pas seulement le tour des foncteurs existants, mais donne du sens à chacun d'eux. Le but n'est pas de les connaître tous mais de les comprendre.

                    Sinon je vais suivre cette discussion car je suis aussi intéressé de voir ce que les uns et les autres vont recommander…

                  • [^] # Re: C'est pourtant évident.

                    Posté par  . Évalué à 2.

                    Les exemples pourraient être implémentés avec de simple fonctions récursives dans un langage impératif. Où est la différence ?

                    Comment est-ce qu'on contrôle la taille de la pile de mémoire de ces fonctions ?

                    • [^] # Re: C'est pourtant évident.

                      Posté par  (site web personnel) . Évalué à 6. Dernière modification le 18 avril 2014 à 23:33.

                      Les exemples pourraient être implémentés avec de simple fonctions récursives dans un langage impératif. Où est la différence ?

                      si tu décides par exemple de calculer factorielle n en impératif tu feras quelque chose comme

                      fact <- 1 
                      pour i = 1..n faire
                       fact <- fact * i
                      fait
                      retourne fact
                      

                      En mémoire c’est constant (tu n’as que deux variable fact et i)

                      En récursif tu aurais quelque chose comme :

                      fact(0) = 1
                      fact(n) = n * fact(n-1)
                      

                      Le problème c’est que la pile grandit beaucoup car en pratique, l’ordinateur déplie le calcul fact(fact(fact…fact(0)))) et quand il y a beaucoup d’appel récursif tu as un débordement de piles.

                      Maintenant dans certain cas, tu peux optimiser un appel récursif, le compilateur le transformant en boucle while. Pour cela il faut que ton code soit récursif terminale. Exemple :

                      fact(0,resultat) = resultat
                      fact(n,resultat) = fact(n-1,n*resultat)
                      
                      fact(n) = fact(n,1) 
                      

                      Tu remarques que avec cette écriture, la dernière opération que fait la fonction fact est de s’appeler elle-même (alors que dans l’exemple précédant elle faisait un multiplication après). Dans le cas d’un compilateur d’un langage fonctionnel digne de ce nom, tous les fonctions avec appel récursif terminal sont automatiquement optimisées en boucle par le compilateur.

                      Donc pour résumé, la récursion est optimisés (dans certain cas) dans les langages fonctionnelles et pas chez les langages impératifs.

                      Cette histoire de récursion terminal est LE point important de la gestion mémoire des programmes fonctionnelles ; toute fonction non terminale est à bannir dans le cas où les appels récursifs sont nombreux. Mais vu que chaque boucle while peut être implémenter en tant que fonction récursives avec appel terminal, on ne perd aucune expressivité par rapport aux langages impératifs.

                      • [^] # Re: C'est pourtant évident.

                        Posté par  . Évalué à 5.

                        Donc pour résumé, la récursion est optimisés (dans certain cas) dans les langages fonctionnelles et pas chez les langages impératifs.

                        C'est faux. GCC le fait, ICC le fait, MS-VC++ aussi. Si tu mets ta fonction sous forme récursive terminale, n'importe quel compilateur optimisant un minimum moderne sait transformer la récursion terminale en boucle. En fait, GCC sait faire de l'optimisation de récursion terminale depuis au moins la version 2.95 (pour rappel, on en est à GCC v4.8).

                        Encore pire, il existe tout un tas de cas où GCC va faire du « pattern matching » et reconnaître une forme récursive d'un algo, ou en tout cas un idiome récursif « facile » à transformer en récursion terminale, et du coup effectuer la transformation…

                        Bref, à moins qu'un compilateur pour langage fonctionnel ait des mécanismes permettant de plus facilement convertir une récursion « normale » en récursion terminale¹, il n'y a absolument aucun avantage en termes d'optimisation à choisir un langage fonctionnel plutôt qu'impératif.

                        J'aime bien les langages fonctionnels, mais une fois que n'importe quel programme, écrit dans n'importe quel langage, a été correctement analysé (analyse lexicale, grammaticale, sémantique, blah), on se retrouve avec un arbre de syntaxe abstrait, c'est-à-dire une représentation intermédiaire qui permet de « visualiser » un programme sous forme de graphe de dépendance (de données, de contrôle, etc.), et autres machins. À partir de là, on peut commencer à effectuer des transformations sur le programme, à « marquer » les nœuds qui pourraient être groupés avec d'autres, ou même supprimés, etc.

                        Par contre, là où les langages fonctionnels ont tendance à briller (je trouve), c'est dans la façon dont ils manipulent plus souvent des « valeurs » que des variables. En gros, souvent les langages fonctionnels proposent un mode qui tient plutôt du « write-once » par défaut, qu'on peut explicitement invalider si on veut des effets de bord, alors que les langages impératifs ont tendance à autoriser les variables à être modifiables par défaut, et il faut ensuite mener une guerre méthodologique pour placer autant de constantes que possibles pour se prémunir contre les effets de bord non désirés.

                        [1] Et pour le coup je ne vois pas du tout comment ce serait possible…

                        • [^] # Re: C'est pourtant évident.

                          Posté par  (site web personnel) . Évalué à 5.

                          C'est faux. GCC le fait, ICC le fait

                          Le C peut le faire ?? Je me coucherai moins bête ce soir !

                          C’est imprécis mais c’est pas faux. Dans les langages fonctionnel la récursion terminal est dans la norme car sans elle tu ne peux rien faire ; je doute fortement qu’elle soit dans une norme C ou C++… Donc tu ne peux pas vraiment te baser dessus si tu veux faire un programme portable.

                          Si j’en croix le site http://blog.fastconnect.fr/?p=174 on a

                          — Le C, ok pour certain compilateur avec certaine option s’optimisation
                          — Pour Java

                          Chaque vendeur de Java VM fait ce qu’il veut. La JVM fournie par Sun, qui est de fait l’implémentation de référence, n’effectue pas cette transformation

                          — Python ne le fait pas
                          — Ruby c’est pas dans la norme
                          — F# le fait (fonctionnel) mais

                          la plate-forme .NET ne sait pas, en général, optimiser correctement les tail calls.

                          Bref, à moins qu'un compilateur pour langage fonctionnel ait des mécanismes permettant de plus facilement convertir une récursion « normale » en récursion terminale¹, il n'y a absolument aucun avantage en termes d'optimisation à choisir un langage fonctionnel plutôt qu'impératif.

                          Pour résumer l’optimisation est présentes chez tout les langages fonctionnelles dans la norme ; présent accessoirement chez quelque compilateur de certains langages impératifs et absent dans beaucoup d’autre.

                          Après personne ne dit qu’il faut utiliser des langages fonctionnel uniquement pour cette optimisation ; mais plutôt que sans cette optimisation tu ne peux pas vraiment faire de programmation fonctionnelle.

                          La programmation fonctionnel est intéressante davantage pour ses variables immutables, son système de type, ses facilité de parallélisation, etc.

                          • [^] # Re: C'est pourtant évident.

                            Posté par  . Évalué à 7.

                            Et Rust ne le supporte pas non plus, parce c'est trop chiant à gérer proprement

                            « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

                          • [^] # Re: C'est pourtant évident.

                            Posté par  (site web personnel) . Évalué à 3.

                            Le C peut le faire ?? Je me coucherai moins bête ce soir !

                            Ça n'a aucun sens. La récursion terminale n'a rien à voir avec le C (comme tu le précise par la suite d'ailleurs) car ce n'est pas une propriété du langage. Un mauvais compilateur C ne le fera pas, un bon le fera. On parle ici d'implémentation du langage et pas du langage lui même. GCC donc le fait, et il le fait pour le C, le C++ et l'Ada. Je pense, mais ne peut pas l'affirmer, que GCC (le middle end du compilateur en fait) le fait pour tous les langages qu'il supporte.

                            Pourquoi n'est pas une propriété du langage? Parce que lorsque l'on écrit N * F (N-1) on exprime tout simplement une expression récursive sans considérer un seul instant l'implémentation et c'est tant mieux car l'implémentation est du ressort du compilateur. A lui de faire au mieux sans nous demander de préciser explicitement quoi que ce soit. Chacun son niveau d'expression et d'abstraction.

                            Donc ok ce n'est pas dans la norme C, ni dans la norme Ada mais c'est ce que fait tout bon compilateur car la récursion terminale n'est pas très difficile à transformer en boucle standard.

                            • [^] # Re: C'est pourtant évident.

                              Posté par  . Évalué à 3.

                              D'ailleurs cela fait parfois apparaitre des bugs étranges, par exemple qui se manifestent en -O0 -g et pas en -O2.

                              Il y a aussi des langages qui garantissent que l'optimisation est faite. Typiquement scheme.

                              Quelqu'un sait ce qu'il en est d'Haskell et d'OCaml par exemple ? Quand on écrit un programme avec une boucle infinie genre un shell, on s'attend à ce qu'il n'utilise pas de plus en plus de mémoire.

                              Please do not feed the trolls

                              • [^] # Re: C'est pourtant évident.

                                Posté par  (site web personnel) . Évalué à 2. Dernière modification le 20 avril 2014 à 00:21.

                                OCaml le gère sans problème au niveau du compilateur, mais la bibliothèque de base n'est pas implémentée pour l'utiliser sur des opérations de base (par exemple List.map) !

                                Dans la pratique personne n'utilise la bibliothèque fournie par défaut, et se tourne vers des librairies tierces, qui elles le gèrent sans problème.

                                Edit : Et par rapport à un langage comme scala, il n'est pas possible de demander au compilateur de vérifier qu'une fonction est bien optimisée. En scala je crois que le mot clef « tailrec » va lever une exception s'il est appliqué à une fonction non optimisée.

                          • [^] # Re: C'est pourtant évident.

                            Posté par  . Évalué à 7.

                            C'est faux. GCC le fait, ICC le fait

                            Le C peut le faire ?? Je me coucherai moins bête ce soir !
                            C’est imprécis mais c’est pas faux.

                            Non, ce n'est pas imprécis. tu dis que les langages impératifs ne permettent pas l'optimisation des fonctions récursives terminales. C'est factuellement faux, car comme le dit Turbo, l'important est de savoir si la forme est récursive « simple » ou bien récursive terminale. Et ceci est indépendant de la nature du langage (fonctionnel, impératif, etc.). En fait, les algorithmes qui transforment une récursion terminale en boucle sont connus depuis un moment, et il y a une équivalence automatique. Donc les compilateurs pour langages comme OCaml (ou F#), ou comme Scala, ou même Common LISP, qui sont tous multi-paradigmes, pourraient tous implémenter la récursion terminale en théorie. Ensuite, pour prendre l'exemple de Scala/Clojure/Java, il n'y a rien, ni dans la JVM, ni dans les langages eux-mêmes, qui empêche la transformation d'une fonction récursive terminale du type :

                            let fact n = 
                                let rec factorial n acc = match n with
                                    | 0 | 1 -> acc
                                    | _ -> factorial (n-1) (n*acc)
                            
                                factorial n 1

                            … en un code bas-niveau du genre (en pseudo-C) :

                            int 
                            fact(int n)
                            {
                                int acc = 1;
                            FACTORIAL:
                                if (n == 0 || n == 1)
                                    return acc;
                                acc *= n;
                                n   -= 1;
                                goto FACTORIAL;
                            }

                            Étant donné que la JVM a une instruction goto pour le bytecode, transformer une fonction récursive terminale en boucle à l'aide d'un goto est trivial. Par contre il est possible que la machine à pile qui est à la base de la JVM ajoute des contraintes sur la façon de générer le code (je ne suis pas du tout expert ni même connaisseur du fonctionnement de la JVM).

                            Concernant Python, Ruby, etc., il y a plein d'optimisations qui ne sont jamais faites dans ces langages, dues à la nature dynamique de ceux-ci (dynamique en termes de types, mais aussi pour la génération de code elle-même). Un truc qui serait optimisé dans beaucoup de cas en C+libC serait par exemple¹ :

                            /* Terrible, terrible way of copying strings. Assumes NULL-terminated strings, makes no checks, etc. */
                            char* bad_strcpy(char *dst, const char *src)
                            {
                                for (size_t i = 0; i < strlen(src); ++i) 
                                    dst[i] = src[i];
                            
                                return dst;
                            }

                            Dans cet exemple, le compilateur C a le droit de transformer le code ainsi :

                            /* Terrible, terrible way of copying strings. Assumes NULL-terminated strings, makes no checks, etc. */
                            char* bad_strcpy(char *dst, const char *src)
                            {
                                size_t src_len = strlen(src);
                                for (size_t i = 0; i < src_len; ++i) 
                                    dst[i] = src[i];
                            
                                return dst;
                            }

                            En Perl/Ruby/Python, de par la nature dynamique de ces langages, il faut nécessairement effectuer cette optimisation à la main. Maintenant, voilà le côté rigolo de ces langages : je connais mal Python, mais j'ai pas mal programmé avec Perl, et un peu avec Ruby. De ce que je vois, aucun de ces langages n'est purement impératif : ils ont tous des constructions fonctionnelles (par exemple : map et grep en Perl). Ils proposent des trucs genre les fermetures (closures en Anglais), qui sont apparues avec les premiers langages fonctionnels. De même, des langages impératifs destinés à la programmation parallèle, comme Habanero (Habanero Java, Habanero C, qui sont des dérivés sur langage X10) proposent des trucs qu'on trouve généralement dans les langages fonctionnels, comme le principe de future — et qui se trouve désormais aussi dans le standard de C++11. Tiens, en parlant de C++, la méta-programmation par templates se fait en effectuant de la programmation fonctionnelle (tout est « write once », tout est valeur).

                            Enfin, j'ai une dernière remarque : les ordinateurs qui suivent le modèle d'exécution de von Neumann² sont par définition des machines qui fonctionnent selon un principe impératif/itératif : il y a un compteur de programme (PC), qui est incrémenté à chaque cycle (ou bien, en cas de branchement, à qui on affecte une nouvelle adresse pour la prochaine instruction). Il n'y a aucune trace de comportement fonctionnel. Tout le génie de ceux qui proposent des langages comme les dialectes de LISP, ML, Haskell, etc., est justement de proposer une façon d'exprimer les programmes sous une forme de bien plus haut niveau, non-impérative (et donc, permettant au programmeur de ne pas penser en termes de ce qu'attend la machine), mais de malgré tout réussir à convertir ces programmes à nouveau sous une forme impérative compréhensible par une machine de von Neumann de façon efficace.

                            Backus (le papa de Fortran) a d'ailleurs expliqué qu'il avait commis une grande erreur en créant Fortran, et que le futur devrait se construire sur la programmation fonctionnelle (le lien que je donne est son discours/sa présentation donné lors de son acceptation pour le prix Turing).

                            [1] Comme la libC est standard, elle est du coup « magique ». Le compilateur a le droit de faire des trucs étranges avec tant que ça respecte la sémantique des fonctions standard.
                            [2] Soit 99% des ordinateurs de la planète à travers les âges. Il y a des machines différentes, qui n'utilisent pas du tout les concepts de compteur de programme, mais elles ont malheureusement toutes échoué à dépasser les autres en termes de performances.

                            • [^] # Re: C'est pourtant évident.

                              Posté par  . Évalué à 10.

                              Rien à voir mais ça fait deux fois que je te vois utiliser un semblant de note de bas de page, alors qu'il y a une syntaxe pour ça : [^1] pour la référence et [^1]: note en fin de commentaire pour la note. Cela donne1


                              1. note 

                              « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

                            • [^] # Re: C'est pourtant évident.

                              Posté par  (site web personnel) . Évalué à 4.

                              Non, ce n'est pas imprécis. tu dis que les langages impératifs ne permettent pas l'optimisation des fonctions récursives terminales.

                              Le problème c’est que ce n’ai pas une question d’optimisation mais de validité.

                              En Erlang le code

                              loop() ->
                                receive 
                                 Msg -> affiche(Msg) ; loop()
                                end

                              est valable ; c’est garantie par le langage que la pile n’explosera pas. En C ce n’est pas garantie. Ça change absolument tout ! Le code ne sera pas plus rapide ou non en fonction de cette optimisation (à peine), il sera par contre correct ou buggé ! Un boucle infinie implémentée avec une fonction récursive terminale est un bug en C car certains compilateurs respectant la norme ou certaine optimisation planteront systématiquement sur ce bout de programme. Ce n’est plus une simple question d’optimisation, mais une question de validité.

                              C’est imprécis parce que je n’ai jamais dit que c’était impossible de le faire en impératif, j’ai juste dit que c’était quelque-chose que, en pratique, on trouve dans les langages fonctionnels et pas impératifs. Et devine quoi, cette optimisation n’est quasiment jamais garantie par la norme des langage objet/impératif et quasiment systématiquement garantie par les celle des langages fonctionnel. Et pourquoi, parce que ce n’est pas vraiment important dans les premiers et fondamentale dans les seconds.

                              • [^] # Re: C'est pourtant évident.

                                Posté par  . Évalué à 3.

                                Ce n'est pas garanti en C, mais en même temps, le C ne définit pas une notion de pile. Il existe de vieilles implémentations de C89 qui n'utilisent pas la pile pour les appels de fonction, sur des architectures un peu exotiques.

                                Mais tu mélanges deux choses :

                                1. La transformation de constructions récursives terminales en boucles, qui n'est pas une exclusivité des langages fonctionnels (c'est une transformation classique, appliquée par la plupart des compilateurs optimisants depuis 15 ans);
                                2. La garantie dans le langage qu'une construction récursive terminale sera transformée en boucle.

                                Que Erlang te donne une garantie sur la transformation des tail calls, c'est très bien. Ça n'indique pas pour autant que la transformation récursion terminale → boucle est toujours effectuée dans les langages fonctionnels. Par exemple, pour Common LISP, presque tous les compilateurs sont capables de le faire, mais tous ne le font pas pour autant. Donc pour le deuxième point, c'est évidemment un plus si le langage garantit la transformation automatique, mais ça n'est (encore une fois) pas quelque chose de fondamentalement lié au côté fonctionnel ou impératif d'un langage. C'est juste que dans un langage fonctionnel pur, il n'y a pas de constructions de boucles, et donc si tu veux pouvoir garantir une certaine performance, il faut implémenter les tail calls sous forme de boucle.

                              • [^] # Re: C'est pourtant évident.

                                Posté par  . Évalué à 3.

                                J'oubliais de répondre à ceci :

                                Le problème c’est que ce n’ai pas une question d’optimisation mais de validité.

                                Le code que tu proposes, que je traduis en C ainsi :

                                void 
                                loop(void)
                                {
                                    char* msg = receive_msg();
                                    puts(msg);
                                    loop();
                                }

                                … est récursif terminal, et est un programme valide en C. Comme je le disais précédemment, le C n'a pas de notion de pile (même si c'était je pense implicite dans les specs avant la normalisation du langage). Donc il n'y a pas de notion de dépassement de pile en termes de langage. Évidemment, en termes d'implémentation, le risque est grand.

                                Ceci étant dit, un langage comme Perl est capable d'effectuer des récursions bien plus profondes que ce qu'une simple pile UNIX propose, ce qui implique que la VM de Perl et le langage lui-même implémentent un truc permettre que cela arrive…

                                • [^] # Re: C'est pourtant évident.

                                  Posté par  (site web personnel) . Évalué à 2.

                                  Non ce n’est pas valide ! Un code valide c’est un code qui marche pour tout les compilateurs qui satisfont la norme ; pas seulement quelqu’un ! Et ce code à clairement un fuite mémoire sans l’implémentation de l’optimisation de la récursion terminale (ce qui est un bug).

                                  La transformation d’appel récursif vers une boucle est une optimisation dans le cas des langages impératifs (ce n’est pas dans la norme) mais une fonctionnalité dans les langages fonctionnelles (garantie par la norme). En théorie ça pourrait être différent, mais en pratique c’est ce que l’on observe.

                                  Cette différence de statut (fonctionnalité/optimisation) n’est pas un simple détail ! Elle change la façon dont on programme. En pratique personne ne programme en récursif en dehors des langages fonctionnels (en tout cas c’est minoritaire), ce n’est pas du tout l’esprit. Oui, tu peux faire du C avec des variables immutable, de la récursion et des pointeurs de fonction, mais ce n’est pas du tout l’esprit du langage.

                                  L’optimisation de la récursion terminale fait partie de l’esprit de la programmation fonctionnel, mais pas de celui de la programmation impérative. C’est la différence de statut, fondamentale dans un cas et accessoire chez l’autre qui à mon sens justifie de dire que l’optimisation de la récursion terminale est plus une optimisation de langage fonctionnel que de langage impératif. Certes c’est une opinion discutable (et cela ne sert à rien d’en discuter infiniment), mais c’est ce que l’on observe en pratique.

                                  • [^] # Re: C'est pourtant évident.

                                    Posté par  . Évalué à 6.

                                    Elle change la façon dont on programme. En pratique personne ne programme en récursif en dehors des langages fonctionnels (en tout cas c’est minoritaire)

                                    Je dirais que ça dépend des situations, typiquement les parcours de graphes sont souvent fait en récursif. Et puis dire que la récursivité est utilisée en fonctionnel est une tautologie vu que c'est obligatoire.

                                    L’optimisation de la récursion terminale fait partie de l’esprit de la programmation fonctionnel

                                    Ce n'est pas vraiment dans l'esprit, c'est juste parce qu'autrement elle serait inutilisable.

                                    « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

                                    • [^] # Re: C'est pourtant évident.

                                      Posté par  (site web personnel) . Évalué à 2.

                                      Ce n'est pas vraiment dans l'esprit, c'est juste parce qu'autrement elle serait inutilisable

                                      Non mais là on joue sur les mots ; c’est un concept fondamentale de la programmation fonctionnel (parce qu'autrement elle serait inutilisable) et un concept annexe dans les autres formes de programmation.

                                  • [^] # Re: C'est pourtant évident.

                                    Posté par  . Évalué à 2.

                                    Non ce n’est pas valide !

                                    Et moi je te dis que si. Il y aura une optimisation pour transformer la récursion en boucle (ou pas), mais ce code est du C parfaitement valide. Il n'y a pas de discussion possible ici : il est écrit en C, qui autorise la récursion.

                                    Et ce code à clairement un fuite mémoire sans l’implémentation de l’optimisation de la récursion terminale (ce qui est un bug).

                                    Non. Car je le répète, le C n'impose pas l'utilisation d'une pile. La fonction loop ci-dessus ne fait absolument aucune allocation — outre le fait qu'effectivement, il faut trouver un moyen de garder les pointeurs msg successifs en mémoire, mais ce n'est pas un problème du langage, car encore une fois, il ne suppose rien concernant l'organisation de la mémoire physique de la machine (ça se trouve, tout est alloué sur le tas, ou bien le PC a une pile de taille infinie, etc.). De même qu'écrire ceci est valide :

                                    while (1) 
                                        ; // nothing

                                    La transformation d’appel récursif vers une boucle est une optimisation dans le cas des langages impératifs (ce n’est pas dans la norme) mais une fonctionnalité dans les langages fonctionnelles (garantie par la norme). En théorie ça pourrait être différent, mais en pratique c’est ce que l’on observe.

                                    Uniquement dans les langages fonctionnels purs, alors. Car j'ai déjà donné l'exemple de Common LISP qui n'a aucune garantie dans la norme à ce sujet. Ensuite, comme tu le dis, il s'agit d'une optimisation pour les langages impératifs, ce qui indique bien que le programme est parfaitement valide lorsqu'il est exécuté sur une machine abstraite (et c'est tout ce qu'on demande à une norme, qui ne peut prévoir tous les progrès architecturaux à venir).

                                    Cette différence de statut (fonctionnalité/optimisation) n’est pas un simple détail ! Elle change la façon dont on programme. En pratique personne ne programme en récursif en dehors des langages fonctionnels (en tout cas c’est minoritaire), ce n’est pas du tout l’esprit.

                                    Oui, évidemment, on utilise beaucoup de constructions itératives dans les langages impératifs. Mais dire qu'en pratique on ne pratique pas la récursion, c'est un peu osé.

                                    L’optimisation de la récursion terminale fait partie de l’esprit de la programmation fonctionnel, mais pas de celui de la programmation impérative.

                                    Transformer un appel récursif terminal en boucle est une obligation pour un langage fonctionnel pur (i.e., qui n'accepte pas les boucles), pour les raisons pratiques que tu as évoquées. Un langage impératif a déjà une façon d'exprimer le résultat final à travers une boucle directement. Dans les deux cas, la transformation d'une récursion terminale vers une boucle est un cas qui n'est pas réservé à un style de programmation : impératif ou fonctionnel, à partir du moment où tu acceptes qu'une fonction peut être récursive, alors la spécification de ton programme dit que toute récursion est légale (je grossis le trait mais j'espère que tu comprends ce que je veux dire). La transformation de RT → boucle est possible dès lors que la forme est respectée, et ceci est indépendant du type de langage. Tu peux tenter de nuancer en disant qu'en C ou C++ ce n'est qu'une optimisation, mais la vérité, c'est qu'il existe énormément d'algorithmes qui sont effectivement exprimés sous forme récursive, et qui, pour des raisons de performance, vont être modifiés jusqu'à atteindre une forme récursive terminale. Ensuite on pourrait bien entendu aller à l'étape suivante « directement » (transformer le tout en boucle), mais comme la plupart des compilateurs savent faire les choses correctement, pourquoi s'emmerder ?

                                    • [^] # Re: C'est pourtant évident.

                                      Posté par  (site web personnel) . Évalué à 2.

                                      Et moi je te dis que si. Il y aura une optimisation pour transformer la récursion en boucle (ou pas), mais ce code est du C parfaitement valide. Il n'y a pas de discussion possible ici : il est écrit en C, qui autorise la récursion.

                                      Valide ou pas, il mène à un bug avec plusieurs compilateur C. Ça te fais une belle jambe que ton programme soit théoriquement valide si en pratique il plante à l’exécution :) J’ai l’impression que la garantie que ton code s’exécute en mémoire bornée n’a aucune espèce d’importance pour toi.

                                      Si la sémantique du langage dit que un fonction qui s’appelle infiniment doit tourner infiniment, alors la sémantique est stupide dans le sens où elle affirme quelque chose d’impossible ! Si une fonction a besoin d’une infinité de mémoire, alors on ne peut pas la faire tourner.

                                      C’est pourquoi en pratique il est important de savoir dans quel cas, on peut faire une récursion infinie. C’est un problème pratique, pas théorique. La norme du langage C ne permet pas de garantir les récursions infinies dans le cas des récursion terminal ; et c’est d’ailleurs pourquoi que certain compilateur transforme des récursion infinies en des programmes qui plantent systématiquement quelque soit la mémoire.

                                      Tu répètes en boucle qu’on peut implémenter la récursion terminal en C, mais j’ai jamais prétendu l’inverse !

                                      1. Je n’ai jamais dis que c’est impossible
                                      2. Je n’ai jamais dis non plus que c’était systématiquement fait dans les langages fonctionnel (j’ai toujours dit généralement)
                                      3. J’ai jamais dis que ce n’était jamais fait dans les langages impératifs, j’ai dit en général que ce n’était pas fait (et c’est vrai pour .Net, java, rust, python…).
                                      4. Tu ne tiens pas compte de la distinction entre être dans la norme du langage ou être simplement une possibilité. Quand tu écrit un code tu veux qu’il marche avec tous les compilateurs et pas seulement un. Avoir une garantie est importante ! Qui te dit que demain gcc trouve une super optimisation incompatible avec l’optimisation des fonctions recursif terminal ? Et dans ce cas là tu fais quoi ? Tu as bien Rust qui n’implémente pas cette optimisation car avec les choix qu’il on fait, cela s’avère trop compliqué. Pourquoi pas gcc demain ?

                                      Mais dire qu'en pratique on ne pratique pas la récursion, c'est un peu osé.

                                      Certes ; mais c’est beaucoup plus rare. Il serait intéressant de savoir combien il y a de fonction récursive dans le noyau Linux. J’avoue en avoir aucune idée, mais à mon avis il n’y en a pas beaucoup.

                                      De plus je doute que beaucoup d’entre eux utilise le fait que la récursion terminal existe pour être correct.

                        • [^] # Re: C'est pourtant évident.

                          Posté par  . Évalué à 4. Dernière modification le 25 avril 2014 à 09:06.

                          Reference:
                          http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#Optimize-Options

                          -foptimize-sibling-calls
                          Optimize sibling and tail recursive calls.
                          Enabled at levels -O2, -O3, -Os.

                          Cool je connaissais pas!

        • [^] # Re: C'est pourtant évident.

          Posté par  . Évalué à 4.

          Et BTW, j'attends toujours de voir le langage maintenable (et donc à la philosophie compréhensible par tout le monde, ce qui tend à exclure les langages fonctionnels qui ne sont pas dans les moeurs), sécurisé, et tout et tout.

          Rust? Bon la 1.0 n’est pas encore sortie mais c’est bientôt.

          Écrit en Bépo selon l’orthographe de 1990

        • [^] # Re: C'est pourtant évident.

          Posté par  . Évalué à 4.

          Et BTW, j'attends toujours de voir le langage maintenable (et donc à la philosophie compréhensible par tout le monde, ce qui tend à exclure les langages fonctionnels qui ne sont pas dans les moeurs), sécurisé, et tout et tout.

          Tout le problème est résumé là. Refuser de se servir de l'électricité parce que que l'on sait se servir de bougies c'est stupide.

          Please do not feed the trolls

  • # Effective C++ par Scott Meyers

    Posté par  . Évalué à 3.

    Je sais, je me repete sur beaucoup de commentaires, mais le livre de Scott Meyers "Effective C++" reste pour moi la reference.
    Il faut absolument le lire en premier.
    Il est (surement) plus oriente C++ que celui la, mais en meme temps, c'est en regardant des exemples que les notions theoriques prennent tout leurs sens :)

    • [^] # Re: Effective C++ par Scott Meyers

      Posté par  (site web personnel) . Évalué à 1.

      Les (M)EC++ sont beaucoup plus techniques. En comparaison, le chapitre OO de Coder Efficacement est plus éloigné du langage, et il sera valable dans tous les langages OO (mainstreams).

      Après, c'est sûr que quelqu'un d'assidu aux forums C++ techniques comme clc++m, dvpz, ou le sdzoc, ou qui connait les FAQs par cœur n'apprendra pas grand chose…

      • [^] # Re: Effective C++ par Scott Meyers

        Posté par  . Évalué à 3.

        Quelle est la différence entre More effective C++ publié en 1995 et Effective C++ (5e édition) publié en 2005 ? Ils se recoupent, ou il faut lire les deux ?

    • [^] # Re: Effective C++ par Scott Meyers

      Posté par  (site web personnel) . Évalué à 6.

      Ce n'est pas vraiment le même genre de bouquin.

      Scott Meyers avec Effective C++ a écrit un bouquin qui a vraiment pour toile de fond le C++ et illustre ça avec des problématiques C++ très précises. Au final, on a un très bon bouquin de C++.

      Là, on a vraiment un bouquin qui est plus pour moi du bon, du raisonnement et du bon sens sur la programmation où l'art d'écrire des programmes. Le C++ sert d'exemple, mais ce n'est pas le sujet principal du bouquin.

      La réalité, c'est ce qui continue d'exister quand on cesse d'y croire - Philip K. Dick

      • [^] # Re: Effective C++ par Scott Meyers

        Posté par  . Évalué à 2.

        Merci d'avoir mis l'accent sur ce point.
        Je pense acheter le bouquin pour pouvoir comparer.
        Surtout qu'il est pas tres cher :)

  • # Cas des constantes

    Posté par  . Évalué à 2.

    Je trouves les exemples concernant la déclaration d'une constante assez maladroits. Après, peut-être que le livre donne plus de détails ou de conseils.

    En présentation, la situation où l'on souhaite une valeur numérique constante.

    Je dirai plutôt « valeur entière constante », puisque l'une des solution consiste à utiliser une énumération.

    On m'a toujours dis de limiter l'utilisation de #define pour déclarer des constantes, en particulier lorsque l'on parle de C++ : effets de bord parce que quasiment personne ne #undef ses #define, pas de notion de d'espace de noms…

    • [^] # Re: Cas des constantes

      Posté par  (site web personnel) . Évalué à 3.

      Cet extrait peut desservir le contenu. Là, ne sont présentées que les syntaxes les plus maladroites. La syntaxe C++11 à préférer est donnée quelques pages plus loin.

    • [^] # Re: Cas des constantes

      Posté par  . Évalué à 2.

      Il y a aussi le cas des bitfields que les enums ne règlent pas du tout.

      • [^] # Re: Cas des constantes

        Posté par  . Évalué à 4.

        Dans linuxmag, il y avait une implémentation en teplate qui avait l'air intéressante.

        Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

  • # Rule of Three

    Posté par  (Mastodon) . Évalué à 6.

    Généralement, la «forme normale de Coplien» (première fois que j'entends ce terme) est utilisé dans le cadre de ce qu'on appelle le Rule of Three : si on implémente un des trois suivants, on implémente les deux autres : constructeur par copie, affectation, destructeur. Et depuis C++11, on a presque le Rule of Five avec le constructeur par déplacement et l'affectation par déplacement. Mais en fait, vu qu'on a maintenant des classes qui gèrent ce genre de cas très bien (shared_ptr, unique_ptr), on a un Rule of Zero (parce que les méthodes par défaut feront exactement ce qu'il faut).

    Du coup, je pense que le livre s'est arrêté avant C++11.

    • [^] # Re: Rule of Three

      Posté par  (site web personnel) . Évalué à 2. Dernière modification le 15 avril 2014 à 15:46.

      La Forme Canonique Orthodoxe de Coplien précède légèrement la Rule of Big Three. Elle traite également du constructeur par défaut. La version à 3 va supposer un constructeur, mais sans le compter (car il faut bien positionner les invariants)

      Quant au passage à 5, il est traité §17. Au moment de l'écriture, on n'avait qu'un seul article sur la règle à 5 ou 0. Et celui que tu pointes (et qui est plus abordable que le premier) n'était pas encore écrit. C'est le genre de chapitre qui pourrait être patché dans les versions électroniques. (Je passe la main à Patricia et/ou Philippe ^^')

      Bref. Non, le livre ne s'arrête pas au C++03. Il y a des éléments de C++11 dedans. Mais pas de C++14 dans mes souvenirs.

      Et de toutes façons, quand les développeurs, ou plutôt les qualiticiens, respecteront correctement la règle de 4/3, ou mieux de 2 (2 == avec du RAII dedans)—C'est à dire en respectant les sémantiques des classes—on pourra pinailler sur la version 5-0 en C++11.

  • # Exceptionnal C++

    Posté par  . Évalué à 4.

    Je conseille aussi "Exceptionnal C++", basé sur le site web "GOTW" pour "Gourou Of The Week".
    Il complète "Effective C++" et "More Exceptionnal C++", notamment en ce qui concerne la gestion des exceptions (mais pas que), comme le titre le laisse à penser.

  • # Réponses en vrac

    Posté par  . Évalué à 10.

    Bonjour,

    Je me dis qu'en tant qu'auteur, je suis relativement bien placer pour user d'un "droit de réponse", que je vous remercie de m'accorder.

    J'ai relevé certains commentaires sur lesquels je souhaite revenir, un peu à la manière de carabiniers d'offenbach. Il ne sont peut être pas dans l'ordre et je m'en excuse.

    Le cas des valeur numérique
    Je précise que je parle bel et bien de valeurs numérique entières constantes dans le livre, mais il faut avouer que, sorti de son contexte comme le cas est présenté ici, cela ne donne vraiment qu'une idée bien piètre de ce qui se trouve dans le livre.

    Car je vais bien plus loin que de dire simplement "voici un #define, un static const et une énumération", je dresse aussi la liste des avantages, des dangers éventuels et des inconvénients des différentes techniques.

    Forme canonique de Coplien, Rule of big three
    J'ai veillé à faire un parallèle très clair entre la forme canonique et la règle des trois grands. J'en ai profité pour parler des (nouvelles!) règles applicables depuis la sortie de C++11 et des pointeurs intelligents. Mais comme l'a fait valoir LM HS, il n'y avait que très peu de ressources, aussi bien en français qu'en anglais, sur les conséquences de certains choix faits en C++11 au niveau de cette règle et de la Forme canonique de Coplien.

    La comparaison (M)EC++ et GOTW

    (More) Effective C++ et Guru Of The Week sont de véritables références, il n'y a pas lieu de discuter de ce point.

    Mais ce sont des références particulièrement pointues qui abordent des points bien précis du C++. L'optique de mon livre est toute autre et tend à rappeler—car je suis sur que tout développeur expérimenté les connait—les principes généraux que l'on a trop souvent tendance à ne pas appliquer parce que "ca sert à rien" selon beaucoup trop de monde.

    Pour ma part, j'estime que le fait d'appliquer directement et avec bon sens les principes que je rappelle dans mon livre permet réellement de rendre le développement plus facile, plus évolutif et plus aisément déboggable.

    Mon but en écrivant cet ouvrage était réellement de rappeler et d'expliquer correctement ces principes, afin que les développeurs soient en mesure de comprendre quand et comment les appliquer. Cette approche est donc bel et bien totalement différente de EC++!

    Coder Proprement

    Voilà sans doute un ouvrage qui manque dans ma bibliothèque, car je ne l'ai pas lu. S'il est orienté vers java, il faut dores et déjà se dire qu'il risque, malheureusement, de prendre les restrictions imposées par ce langage comme acquises et définitives.

    Il ne s'agit pas de remettre la philosophie imposée par java et par C# en question ici, car ce sont des philosophies tout ce qu'il y a de plus cohérentes. Mais il s'agit de rappeler que C++ a une philosophie différente, plus permissive, imposée par des choix conceptuels différents.

    Limiter l'approche orientée objet à la seule philosophie de java (ou de C#) empêche bien souvent de prendre conscience du problème plein et entier tel qu'il se présente, tend à "amputer" la vision que l'on peut avoir du problème de pans entiers de celui ci.

    J'ai choisi C++ à dessein, car non seulement sa philosophie est plus permissive, mais il permet aussi de mettre en place des solutions "originales" et élégantes.

    • [^] # Re: Réponses en vrac

      Posté par  . Évalué à 1.

      Les 3 façons de déclarer des constantes, dans cet article, s'écrivent aussi exactement comme cela en C, et avec les mêmes avantages et inconvénients. Fallait-il vraiment mettre cet exemple-la en avant dans l'article ? Ce n'est pas très alléchant.

      • [^] # Re: Réponses en vrac

        Posté par  (site web personnel) . Évalué à 2. Dernière modification le 17 avril 2014 à 17:41.

        En C tu n'auras pas ces choses là:

            enum type { un, deux, MAX__};
            char const* const names[] = {"un", "deux"};
            static_assert(MAX__, std::extend<decltype(names)>::value); // écrit de mémoire

        Ni

            enum class type { a, b, c, MAX__};

        Ni mon préféré en C++98/03

            struct TypeEnum {
                enum type { un, deux, MAX__};
                TypeEnum & operator++() {
                    assert(m_value < MAX__);
                    m_value = type(m_value + 1);
                }
                // + constructeurs, fonctions pour convertir, ...
            private:
                type m_value;
            };

        Mais je reconnais volontiers que ce n'est pas forcément l'extrait le plus alléchant. Mais bon. On trouvera toujours à redire car au fond, l'Oncle Bob a déjà décrit SOLID en long et large, ou encore le RAII est un classique des FAQs, etc.

        • [^] # Re: Réponses en vrac

          Posté par  (site web personnel) . Évalué à 3. Dernière modification le 17 avril 2014 à 16:27.

          La prévisualisation du code est toute pourrie chez moi. En espérant que cela rende mieux au final

          l'anti-sèche au bas des commentaires rappelle la syntaxe pour le code (avec l'exemple du ruby) et la page wiki aide-édition rappelle qu'il vaut mieux spécifier le langage.

          Un modérateur pourrait passer par là pour remettre au mieux en forme.

        • [^] # Re: Réponses en vrac

          Posté par  . Évalué à 4. Dernière modification le 17 avril 2014 à 16:53.

          En fait, il faut mettre trois ' à l'envers ([altgr]+è sur un AZERTY) et c++ en minuscule, et laisser une ligne avant et une ligne après. Pour fermer, trois ' à l'envers. Ex :

          '''c++
          int a;
          '''

          Donne :

          int a;
        • [^] # Re: Réponses en vrac

          Posté par  (site web personnel) . Évalué à 1.

          Je me suis laissé avoir par le bouton de la zone d'édition.
          Merci à vous deux. Je saurais pour la prochaine fois.

  • # Policy-based design

    Posté par  (site web personnel) . Évalué à 3.

    Hello, j'en profite pour faire partager mon enthousiasme pour
    Modern C++ Design: Generic Programming and Design Patterns Applied

    Le bouquin m'a appris les policy-based design, une révolution personnelle en ce qui concerne le code C++ : modulaire, quasiment entièrement vérifié à la compilation. Un grand gain de temps, et du code bien plus joli, malgré l'avalanche de templates.

    • [^] # Re: Policy-based design

      Posté par  (site web personnel) . Évalué à 1.

      (Encore un Addisson-Wesley … :) )

      Dans un genre proche et différent à la fois, tu as le CRTP, ou encore Mixin-Layers façon Smaragdakis (boudiou! plus de 10ans après, je sais toujours l'écrire…)

      Pour le MC++D, en fin de compte, ce qui a été retenu dans boost (et par continuité) dans le dernier standard a des approches différentes (pour les smart-pointeurs, les listes de types, …)

    • [^] # Re: Policy-based design

      Posté par  (site web personnel) . Évalué à 4.

      Un grand gain de temps

      Sauf à la compilation /o\

      Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.