Guillaum a écrit 472 commentaires

  • [^] # Re: Donc pour résumer…

    Posté par  (site web personnel) . En réponse à la dépêche C++17, Genèse d’une version mineure. Évalué à 7.

    Attention, je me sers d'Haskell dans ce post pour donner des exemples, mais je ne veux pas me lancer dans un débat C++ versus un autre langage.

    As-tu des exemples de langage dans lesquels ce que tu fais manuellement est fait automatiquement ? J’ai un peu de mal à suivre où tu veux en venir.

    En haskell, un type Square qui peut s'afficher, se serializer, se comparer à un autre, tester l'égalité et se hasher.

    > data Square = Square Float deriving (Show, Serialize, Ord, Eq, Hashable, Generic)
    > s = Square 10
    > show s
    "Square 10.0"
    > encode s
    "A \NUL\NUL"
    > s == Square 10
    True
    > s == Square 11
    False
    > s < Square 10
    False
    > s < Square 11
    True
    > hash s
    839657739146560044

    Et j'en passe bien d'autres. Tu peux bien évidement surcharger toi même le comportement si tu le désires, mais les comportement par défaut de Show, Serialize, Eq et Hashable me conviennent parfaitement. Je ne sais pas faire cela en C++. Note que ce n'est pas quelque chose écrit en dure dans le compilateur, tu es libre de crée toi même tes propres "classes" génériques (par contre c'est pas forcement là où haskell est beau ;)

    Euh, non, le découpage en petites unités fonctionnelles est très présent en C++. C’est ce que tout le monde recommande aujourd’hui (loi de Déméter, à l’esprit de tous les devs c++ compétents). C’est parce qu’il y a deux fichiers que c’est « trop compliqué de créer un classe » ? C’est un problème d’environnement de dev dans ce cas, pas de langage.

    Cela a beau être faisable, ce n'est pas ce qui est fait dans BEAUCOUP de base de code que j'ai observé (libre ou pas). Mais cela n'est pas le point.

    Je ne parle pas de découper en petites unités fonctionnelles, heureusement, c'est la base. Je parle de faire autant de petites unités fonctionnelle qu'il existe de cas de figure, pour se garantir de ne pas mélanger des tortues et des rats.

    Un exemple. Tu as un type template template <typename T> class Vector3 dont tu veux te servir pour representer des positions, des directions, des couleurs (dans différents espaces), avec différents opérations possibles. Plusieurs solutions, très souvent celle que je rencontre c'est d'utiliser la même classe pour representer tout cela. C'est ce que je reproche, car j'aimerais un type par possibilité pour éviter des opérations du genre point + couleur. Hors en C++ cela devient très vite lourd de crée un nouveau type qui ne fait que déléguer. (Note que pour ce problème tu peux t'en sortir facilement avec des types phantom, mais ce n'est pas toujours le cas).

    Euh, non. Tu peux très bien mettre un type non default constructible dans un unique_ptr. Depuis c++11, on peut même dans un vector, suivant comment on l’utilise (tant qu’on n’utilise pas resize). En plus, comme on peut le defaulter quand ça a du sens, ce n’est franchement pas la mer à boire.

    Tu ne peux pas mettre un non default constructible comme deleter de unique_ptr. (i.e.: le second argument du template), même dans le cas où ton unique ptr est default initialisé avec nullptr. Ce qui te force à crée un constructeur par défaut à ton deleter et à gérer ce cas. Ce n'est pas la mer à boire je te l'accorde ;)

    À mettre en balance avec le fait que C++ offre un const vérifié statiquement. Bien utilisé, c’est beaucoup de bugs ou de prises de tête en moins.

    Je suis d'accord avec toi. J'aurais aimé l'inverse, du const par défaut et un truc genre mutable pour ce qui peut changer.

    Pour le reste de tes critiques, je suis assez d’accord (sauf le pattern matching dont je trouve qu’à part l’effet trocool, ça ne sert pas à grand chose).

    Disons que sans type algébrique, le pattern matching ne sert en effet pas à grand chose. Mais quand tu as des types algébriques et un pattern matching en profondeur, il permet d'expliciter tous les cas à traiter, avec une garantie statique à la compilation que tu as bien tout traité et une grande lisibilité de code.

    Mais la question qui est toujours en suspens, c’est quelle alternative à c++ ? Pour du dev jetable, plein. Pour un truc censé durer 10 ans (c’est pas si long que ça dans le domaine du développement logiciel), il y a c# et java, si on peut se permettre le GC. Sinon…

    Je n'ai jamais prétendu proposer une alternative au C++, d'ailleurs, comme le disait, écrire du C++ c'est ce qui me paye et j'y trouve certain avantage (au c++, et aussi au fait que on me paye ;)

    Je n'ai pas l’expérience pour te répondre sur la pérennité du truc. J'ai vraiment tendance à penser que des langages avec des outils de packaging comme ce que on trouve en Rust ou Haskell vont te permettre de rebuilder ton projet dans les même conditions dans 10 ans, alors que ressortir un projet C++ qui a 10 ans et essayer de le rebuilder, c'est toujours une horreur de cache cache avec les dépendances. Pour moi c'est aussi une grande faiblesse du C++, c'est que, sauf si ta recette de build inclue TOUTES les dépendances ainsi que le compilateur, tu n'es pas garanti d'avoir un build identique 6 mois plus tard.

  • [^] # Re: Donc pour résumer…

    Posté par  (site web personnel) . En réponse à la dépêche C++17, Genèse d’une version mineure. Évalué à 1.

    Non, certains trucs n'étaient pas dis ;)

  • [^] # Re: Donc pour résumer…

    Posté par  (site web personnel) . En réponse à la dépêche C++17, Genèse d’une version mineure. Évalué à 10.

    Aller, j'en ajoute (C++ est mon outil de travail 8h par jour depuis plus de 10 ans) en me focalisant sur les problèmes qui m’embêtent vraiment au quotidien et pour lesquels il n'y a pas de solution simple. J'essaye de rester objectif ;)

    • Les casts implicites. Plus particulièrement le fait que les flottants, les ints, les bools, les enums, et j'en passe, c'est la même chose. Le downcast implicite d'une classe vers une autre. Je sais que c'est la philosophie du langage. Combien de fois je me suis fais manger par un int négatif convertit en unsigned…
    • L'absence de programmation générique. J'aimerais c'est pouvoir automatiquement dériver des comportements pour mes classes. À la louche, l'itération, l'affichage, la sérialisation, la comparaison, les relations d'ordre, le hash, l'enumeration, … Je recode ces trucs là pour presque chaque classe et c'est du temps perdu, du code en plus, des tests en plus, des bugs en plus, de la maintenance en plus.
    • L'absence de types algébriques (i.e. des union typés). Je dirais à la louche que 99% des choses que je traite avec de l'héritage (ou des gros hacks degeux) sont des problématique de types algébriques. L'héritage virtuel sera ici trop verbeux et impose des allocations sur le tas (on peut s'en sortir autrement, mais c'est tordu). La solution à base d'union et de flag est un piège dans lequel on ne tombe qu'un fois tellement c'est l'horreur à maintenir après.
    • Pas de pattern matching.
    • Pas de concepts ;)
    • Un système de type limité et hyper verbeux ce qui fait que on essaye de ne pas créer de nouveau type parce que c'est complexe. Je le retrouve tous les jours au travail ou en formation, un développeur C++ est heureux quand il a une classe suffisamment générique pour traiter tous ses cas, alors que moi je serais heureux quand j'aurais 100 classes suffisamment spécifiques pour traiter tous mes cas avec une bonne sécurité. Mais je ne le fais pas parce que maintenir 100 classes en C++ versus une, c'est l'horreur. Faire de la délégation en C++ c'est lourd.
    • Les valeurs par défaut. Beaucoup de classes de la STL imposent que votre type soit default constructible, comme par exemple le deleter de std::unique_ptr. Cela force à mettre des constructeur par défaut complètement abscons à beaucoup d'endroits.
    • Une absence totale de système de gestion de descendances, librairies, packaging. C'est l'horreur de taper à la main la compilation / installation de 25 librairies tierces.
    • Tout est mutable par défaut ;( Là c'est clairement un point subjectif de ma part, mais je n'aime pas.
    • La communauté. N'allez surtout pas sur IRC si vous n’êtes pas un dieu du C++, vous vous ferez allumer et traiter d’incompétent. Expérience personnelle que j'ai mal vécue : je suis aller sur #C++ sur irc.freenode.net pour discuter une subtilité du langage qui m'avait étonnée et on m'a envoyer paître en me traitant d’incompétent qui ne comprenait pas la philosophie du langage. J'ai fais l'erreur dans mon texte de dire que j'avais rencontré cette subtilité pendant le cours de compilation que je donne à l'université et ils m'ont descendu en expliquant que soit j’étais un troll, soit j'étais un mauvais enseignant, mais qu'il n'était pas possible d'être aussi mauvais et que je ferais mieux de me pendre ou de lire un livre de C++ pour débutant.

    Alors pourquoi C++ est-il mon outil de travail si ce langage est si mauvais ? Parce qu'en premier lieu, tous les langages du monde sont mauvais, en second lieu, c'est le seul à l'heure actuel qui m'apporte plusieurs choses :

    a) Les performances, qui sont vraiment critiques dans mon métier.
    b) Les librairies. Toutes les libraires utiles dans mon métier sont écrites en C++, sans interface C.
    c) L'historique, l'outil actuel est écrit en C++ et on ne peut pas tout réécrire from scratch.
    d) Les gens formés. Trouver quelqu'un qui sait aligner deux lignes de C++, c'est facile (même si il le fait sans doute mal, il le fera). Trouver quelqu'un qui sait aligner deux lignes de Rust / Haskell / OCaml / …, c'est bien plus dur.

    Mais clairement, sur un projet neuf, où il n'y a pas autant de librairies métier à gérer et où les performances ne sont pas autant un problème, j'aimerais faire un autre outil.

    Sinon il y a quand même un truc que j'aime en C++, c'est les "int" dans les templates. Cela permet de faire un tout petit peu de typage dépendant et ça c'est marrant ;)

  • [^] # Re: Un retour aux sources?

    Posté par  (site web personnel) . En réponse au journal Réparabilité de l’électroménager : SEB s’engage. Évalué à 6.

    Question bête, mais tu sais comment quel est le condensateur à changer ?

  • [^] # Re: Lire la documentation

    Posté par  (site web personnel) . En réponse au sondage Ce que je déteste le plus en informatique / programmation / codage c'est... :. Évalué à 4.

    J'ajouterais, en plus de ma remarque sur la doc pour PhD en math faites autre part dans les commentaires, les documentations pas à jour (et donc fausse). C'est surtout le cas dans des langages avec un typage un peu "dynamique" où certains arguments peuvent prendre un peu n'importe quoi, du genre un flag qui prend soit None, soit une fonction, soit des chaînes de caractère pour décrire une méthode, et où la documentation liste la mauvaise liste de possibilité et ne documente pas le prototype de la fonction que on doit passer ;)

  • [^] # Re: Le code impératif

    Posté par  (site web personnel) . En réponse au sondage Ce que je déteste le plus en informatique / programmation / codage c'est... :. Évalué à 7.

    Oui !

    Mais si il y a une chose que je déteste avec Haskell, c'est la documentation pour docteur en informatique fondamental.

    J'adore le module Bifunctor mais je met au défi un humain normal (comprendre, un mec qui est au moins docteur en informatique avec +10 ans d’expérience en programmation) de comprendre ce que fait ce module à la première lecture. Alors que merde, un exemple tout simple au début n'aurait pas fait de mal, genre :

    -- Application to tuple :
    Prelude Data.Bifunctor> first length ("hello", 10)
    (5,10)
    Prelude Data.Bifunctor> second (*2) ("hello", 10)
    ("hello",20)
    Prelude Data.Bifunctor> bimap length (*2) ("hello", 10)
    (5,20)
  • [^] # Re: Mauvaise connaissance du c++

    Posté par  (site web personnel) . En réponse au journal Gestion de l'erreur - C++ - std::optional. Évalué à 2.

    De part leur nature, tu changes le type de Checked Exceptions retourné ou tu en rajoutes une : ça impact tout la pile.

    C'est là que le design est mal fichu. Cela impacte toute la pile le temps que tu règles le problème en gérant l'exception à l'endroit qui te semble pertinent dans la pile, si tu veux le gérer localement, et bien fait le localement (et le reste de la pile ne sera pas impactée). Si tu veux le faire 10 étapes plus haut, alors il est normal que cela impact les 10 couches intermédiaires.

  • [^] # Re: Liberté de l'utilisateur

    Posté par  (site web personnel) . En réponse au journal Gestion de l'erreur - C++ - std::optional. Évalué à 3.

    Moué, en pratique cela me choque la fonction distance qui prend des Maybe en paramètre… Je ne sais pas si j'ai quelque part dans mon code des fonctions qui prennent des Maybe en paramètre (si ce n'est des combinateurs).

    Alors, si on suppose que square :: Float -> Maybe Float et que sqrt :: Float -> Maybe Float, mais bon, parce que on est pervers, je ferais :

    distance :: Float -> Float -> Float -> Float -> Maybe Float
    distance x1 x2 y1 y2 = do
        d1 <- square(x2 - x1)
        d2 <- square(y2 - y1)
        sqrt (d1 + d2)

    distance :: Float -> Float -> Float -> Float -> Maybe Float
    distance x1 x2 y1 y2 = sqrt =<< ((+) <$> square (x2 - x1) <*> square (y2 - y1))

    Mais là on nous prendrais pour des malades ;)

    Si cela intéresse quelqu'un, je peux expliquer ce qu'est ce bordel. Cela fait peur, mais les trois opérateurs =<<, <$> et <*> sont tellement utilisés tous le temps en haskell qu'il sont un peu comme le =, les [] et le -> du C.

  • [^] # Re: Mauvaise connaissance du c++

    Posté par  (site web personnel) . En réponse au journal Gestion de l'erreur - C++ - std::optional. Évalué à 3.

    C'est dingue comme on peut avoir des avis si divergent, c'est interessant.

    Dans mon cas de figure je veux être au courant que l'API a changée, et pour moi la possibilité d'une exception en plus ou en moins cela fait partie de l'API, c'est tout aussi important que si un argument est ajouté à une méthode et je veux que cela m’empêche de compiler.

    Après si tu laisses les exceptions remonter une pile d'appel de 12 fonctions, c'est qu'il y a aussi sans doute un problème de conception quelque part et il serait intéressant de revoir l'encapsulation.

  • [^] # Re: moche ?

    Posté par  (site web personnel) . En réponse au journal Gestion de l'erreur - C++ - std::optional. Évalué à 4.

    Ba si…

    Si tu as un std::optional<A> nommé value et une fonction de A dans std::optional<B> nommée f. Alos bind(value, f) te renvoie un std::optional<B>. Soit il y avait quelque chose dans value et il a appliqué f sur ce quelque chose, soit il n'y avait rien, et il renvoie un std::optional<B>.

    Le cas vide est donc géré et repoussé à la prochaine étape. Un jour, en fin de chaîne, une décision sera prise sur quoi faire du optional vide, une valeur par défaut, avec value_or, ou un comportement different, avec optionalCase.

  • [^] # Re: Liberté de l'utilisateur

    Posté par  (site web personnel) . En réponse au journal Gestion de l'erreur - C++ - std::optional. Évalué à 4.

    Ça serait vrai si la gestion des erreurs par les optional était une solution universelle et consensuelle. Personnellement, j'ai l'impression que ça ressemble à une fausse bonne idée, qui complexifie l'interface et qui impose à l'utilisateur un paradigme de gestion des erreurs qu'il ne maîtrise peut-être pas, qu'il n'apprécie peut-être pas, ou qui n'est peut-être pas pertinent dans le cadre de son projet.

    Je ne vois pas en quoi cela complexifie l'interface ? D'un coté nous avons une interface implicite où les erreurs sont gérées par des valeurs sentinelles ou des flags, où il faut lire la documentation d'une fonction en profondeur pour comprendre son comportement et avoir de la rigueur lors de l'utilisation pour ne pas se tromper et pour laquelle tout le monde réinvente de la gestion d'erreur tous les jours. Un jour le flag de retour est un bool qui vaut true pour ok, false sinon, l'autre jour c'est l'inverse, le troisième jour c'est un int. De l'autre nous avons une fonction dont le prototype te renseigne beaucoup sur son comportement et pour laquelle tu ne peut pas te tromper en l'utilisant. Et la seule complexification de l'interface c'est l'utilisation d'une classe optional contenant deux méthodes, map et bind (non fournies en standard je te l'accorde). On peut aussi utiliser l'approche par exception sur le .value() comme proposée par d'autres.

    Pour le coté consensus, c'est tout de même une approche prise dans de nombreux langages : Rust, Swift, OCaml, Haskell, entre autre… Avec une approche plus ou moins poussée.

    float distance(float x1, float x2, float y1, float y2) {
       return sqrt(square(x2-x1).value() + square(y2-y1).value()).value();
    }

    Pourquoi n'as tu pas poussé le vice de ton exemple jusqu'à mettre un optional sur le + pour montrer à quel point les optionals sont complexes ?

    La gestion d'optional n’apparaît que dans les fonctions qui peuvent générer des erreurs, et dans ces fonctions, il faut traiter les erreurs et soit c'est fait avec des optionals, soit avec autre chose, quoi qu'il en soit, cela apparaitra. Soit il n'y a pas d'erreur à traiter, et dans ce cas là pas de problème. Ainsi ta fonction distance qui est toujours définie, sauf en cas de nombre infinis, peut être écrite sans optional. Après si les NotANumber t’embêtes, alors tu pourras mettre des optionals pour protéger.

  • [^] # Re: Liberté de l'utilisateur

    Posté par  (site web personnel) . En réponse au journal Gestion de l'erreur - C++ - std::optional. Évalué à 2.

    si on fait if( machinOptional ), on peut ensuite utiliser *machinOptional, le value() fait une verif supplémentaire qui n'est pas nécessaire.

    Oui. C'est un exemple pédagogique pour montrer que cette approche est, à mon sens, lourde. On est pas à un test dans un exemple pédagogique, mais promis, la prochaine fois, je le ferais mieux ;)

  • [^] # Re: moche ?

    Posté par  (site web personnel) . En réponse au journal Gestion de l'erreur - C++ - std::optional. Évalué à 2.

    C'est assez moche comme code. En Ocaml, le type optionnel est un type somme, ce qui oblige à le décomposer, donc, c'est impossible de l'oublier ou de tomber sur un pointeur nul. Il manque ce filtrage à C++ pour avoir toutes la puissance du truc.

    C'est exactement ce que je propose à la fin de l'article non ? Sauf que comme on ne peut pas "pattern matché" en C++, je propose deux fonctions utilitaires, map et bind qui font les tests et qui appellent les lambdas associés.

  • [^] # Re: Liberté de l'utilisateur

    Posté par  (site web personnel) . En réponse au journal Gestion de l'erreur - C++ - std::optional. Évalué à 3.

    std::optional<D> foo(Arg arg)
    {
      try{
        return make_optional(h( g( f(arg).value() ).value())) ;
       }
       catch(Machin e)
       {
        return std::optional<D>();
       }
    }

    C'est pas mal en effet. J'aime moins car je trouve que c'est plus verbeux / complexe que l'approche à base de map et bind et que on peut facilement oublier de faire le traitement. Cependant j'admet que c'est une solution intéressante avec ce qui est fournit par défaut.

  • [^] # Re: Liberté de l'utilisateur

    Posté par  (site web personnel) . En réponse au journal Gestion de l'erreur - C++ - std::optional. Évalué à 3.

    Je comprendrais le mécanisme des optional s'il y avait une conversion implicite de optional vers T, ce qui permettrait, au choix, d'utiliser la fonction de manière transparente, ou de gérer les erreurs si le contexte le demande.

    Personnellement je déteste les conversions implicites, c'est subjectif, mais je sais que je vais me faire avoir à un moment donné.

    Si tu publies une bibliothèque dont la gestion d'erreur repose sur des optional, tu forces les utilisateurs à adopter cette méthode, même si ça ne correspond pas du tout à la manière dont, en interne, ils gèrent les erreurs. Je ne sais pas si le jeu en vaut la chandelle.

    En raisonnant de cette manière on ne fait aucune évolution et on reste à faire du C avec classes comme l'était les premières versions de C++.

  • [^] # Re: C++ et exceptions

    Posté par  (site web personnel) . En réponse au journal Gestion de l'erreur - C++ - std::optional. Évalué à 3.

    Pas avec les améliorations proposées dans ce journal ;)

  • [^] # Re: C++ et exceptions

    Posté par  (site web personnel) . En réponse au journal Gestion de l'erreur - C++ - std::optional. Évalué à 5.

    Merci pour la précision des librairies qui utilisent des exceptions.

    Bref, affirmer que la bibliothèque standard utilise les exceptions dans des cas limités est erroné. Elle utilise les exceptions pour les cas exceptionnels, ce qui est la raison d'être des exceptions.

    La notion de cas exceptionnels est vraiment dépendante du point de vue. C'est toujours un grand moment de cassage de tête de savoir ce que on fait passer en exception et ce que on fait passer en std::optional ou autre méthode.

  • [^] # Re: contrats

    Posté par  (site web personnel) . En réponse au journal Gestion de l'erreur - C++ - std::optional. Évalué à 2.

    apparition de démons naseaux

    Belle traduction, j’apprécie ;)

    Si l'on prend le cas d'une fonction mathématique (cad "pure" en info) on pourrait dire que le contrat est que les paramètres sont dans le domaine de définition. Ça n'implique pas en soit l'apparition de démons naseaux si l'appelant est en dehors, et il est tout à fait convenable (et même préférable) de réagir par exemple en levant une exception dans une implémentation.

    Quand une fonction n'est pas définie pour tout son domaine, il y a plusieurs solutions :

    • On réduit le domaine de définition, on rendant les types en entrée plus contraints, par exemple, on pourrait imaginer une fonction division avec le prototype suivant int division(int a, nonZeroInt b), où nonZeroInt est un type int qui ne peut pas contenir zéro. Mais cela force l'utilisateur à convertir et finalement cela repousse le problème plus haut dans le code.
    • On augmente le domaine de sortie, en mettant par exemple un std::optional en valeur de retour, ainsi division deviendrait std::optional<int> division(int a, int b). Mais on repousse le problème plus bas dans le code.
    • On lève une exception. C'est ce que tu proposes et dans de nombreux cas c'est une bonne solution, mais je ne peux pas m’empêcher de penser qu'un jour cela va péter au moment où on ne s'y attend pas.

    C'est un vrai problème et à chaque cas de figure il y a une solution qui est plus ou moins adaptée.

    Une solution que j’apprécie vraiment, mais qui est peu utilisé dans la vraie vie c'est l'utilisation de type rafinés. Tu peux ajouter des contraintes sur les types de ta fonction et laisser le compilateur prouver que ces contraintes ne seront jamais violée. Sinon il peut te monter dans quel cas ton code est faux ou simplement te laisser dans le doute en t’annonçant qu'il n'a pas réussis à faire la preuve. En Haskell il y a Liquid Haskell qui est très amusant à utiliser, mais je n'ai pas de retour sur l'impact que cela peut avoir sur un gros projet.

  • [^] # Re: C++ et exceptions

    Posté par  (site web personnel) . En réponse au journal Gestion de l'erreur - C++ - std::optional. Évalué à 5.

    Je ne fais pas du tout de C++ et du coup ce passage m'intrigue profondément. Je ne veux pas lancer de troll hein, mais ça m'aurait vraiment intéressé de connaître ces raisons.

    Alors, en premier lieu, je dois corriger un point faux (imprécis) de mon article. La librairie standard c++ utilise les exceptions, mais dans certains cas limités, comme une allocation ratée. Mais l'ouverture d'un fichier, la recherche d'un élément qui n'existe pas, …, sont gérées avec des fonctions de test is_open, ou des valeurs sentinelles conteneur.end().

    Concernant l'usage, certains se plaignent des performances ou de l'augmentation de la taille du code, mais c'est très controversé et discutable en fonction du contexte de travail.

    Un autre problème, soulevé dans mon journal, est que les exceptions ne sont pas du tout listées dans le prototypes des fonctions, et donc qu'il est difficile de savoir, sans lire la documentation, qu'une fonction peut lancer une exception, et les choses peuvent évoluer sans prévenir.

    Un autre point important est la difficulté d'écrire du code qui résiste bien aux exceptions, l'idée étant que si une exception est lancée, tu dois pouvoir te retrouver dans un état stable et prévisible après récupération de l'exception, sinon il ne sert à rien de survire à l'erreur si c'est pour se retrouver dans un état indéterminé.

  • [^] # Re: Ouai bof

    Posté par  (site web personnel) . En réponse au journal Gestion de l'erreur - C++ - std::optional. Évalué à 2.

    Tout à fait d'accord sur l'histoire du pointeur null, en java cela aidera pas. En C++ tu peux éviter de mettre des pointeurs dans des optionnels et dans ce cas là tu auras moins de problème.

    Personnellement mon optional maison lève une exception si on lui met un pointeur null dedans.

  • [^] # Re: Pas uniquement string

    Posté par  (site web personnel) . En réponse au journal Switch, chaîne constante et c++. Évalué à 4.

    Bref tu peux penser ce que tu veux, mais si tu oses aller donner ton explication à n'importe quelle personne en sécurité (moi par exemple, qui a passé les 12 dernières années dans le milieu) tu vas leur filer un arrèt cardiaque tellement ton explication ne tient absolument pas la route et démontre un manque de compréhension des enjeux.

    Je lis avec intérêt tes commentaires sur linuxfr, il en ressort que tu es compétant et surtout, tu as un avis différent de l'avis général, ce qui est souvent source de discussions intéressantes, mais qu'est ce que tu peux être impoli, c'est dingue ;)

    Tu te permets de me traiter d’incompétent alors que tu n'as simplement pas compris mon discours, ou alors je me suis mal exprimé, on cherchera qui est le moins compétant en communication plus tard… Et tu te sers d'un argument d'autorité ce qui est petit.

    Je ne veux absolument pas dire que le non initialisé est mieux ou moins bien que le initialisé par défaut, je veux dire que les deux sont dangereux. Mon argument est que les trucs par défaut arbitraire c'est dangereux. Si en tant que développeur tu veux le même comportement que celui par défaut, très bien, mais cela pourrait valoir le coup d'être explicite (çà c'est mon avis et c'est subjectif). Mais si tu ne veux pas le comportement par défaut mais qu'il apparaît parce que tu as fais une erreur et que tu n'est pas prévenu, et bien c'est faux et dangereux, et çà c'est factuel (enfin je pense, j'ai tort ?).

    Oublions le cas des pointeurs qui est une particularité du fait de la valeur par défaut qui est nullptr et cela a plein d'implication marrantes que nous débattons. Mais qu'en est il d'autres types comme les entiers, les booléans où les char ? le \0 par défaut sur un char a certainement autant de chances de ne pas faire planter ton programme qu'un 42 ou un 12 par défaut, mais il est tout aussi faux si ce zéro n'était pas ton intention.

    Sauf que le truc par défaut, le compilateur ne vas pas râler (son boulot c'est de mettre une valeur par défaut) et ton programme aura un résultat stable. Alors qu'une valeur non initialisé, le compilateur risque de râler et le programme n'aura pas un résultat stable, ce qui donne plus de chance de ce rendre compte du problème avant que le truc ne parte en production. J'ai eu un enseignant qui disant que le pire qui pouvait arriver dans un bug c'est que le programme ne plante pas, et le mieux c'est que cela plante, parce que cela permet de réaliser qu'il y a un bug. Et à ce niveau les valeur non initialisée augmentent les chances que cela plante comparée aux valeur par défaut arbitraire, d'où ma remarque initiale qui a lancée le "débat".

    Bref, au final on est d'accord, non initialisé cela pue d'un point de vu sécurité, cela pue vraiment ! Mais par défaut, si ce par défaut n'est pas la valeur que tu veux, je trouve que cela pue aussi.

  • [^] # Re: Pas uniquement string

    Posté par  (site web personnel) . En réponse au journal Switch, chaîne constante et c++. Évalué à 4.

    L'initialisation par défaut a du sens et c'est évident. Je sais que les programmeurs Haskell adore rappeler au autres à quel point leur langage est supérieur à la plèbe de ce monde car il n'autorise pas l'initialisation, mais la tu fais preuve d'un poil de mauvaise fois :)

    Désolé, mon commentaire était sans doute puant à la mode élitiste Haskell, ce n'était pas mon but et je me suis mal exprimé.

    Je communique mon intérêt pour Haskell, et que ce langage influence beaucoup ma façon de travailler, mais au final le C++ reste mon outil de travail et d'enseignement. C'est un langage que j'apprécie, malgré ses défauts. Il me permet de faire mon travail alors que je ne pourrais pas le faire en Haskell pour des raisons de performance.

    Ai-je tort d'essayer d'appliquer des principes inspirés d'autres langages pour rendre mon code C++ plus robuste ? Ai-je tort de critiquer dans un but d'amélioration, un langage que j'utilise tous les jours? Par exemple, je suis très heureux de l'arrivée des std::optional en C++17, mais je trouve que l'api proposée est un échec total puisqu'elle permet beaucoup d'erreurs qu'une api différente aurait évitée et à ce niveau ils auraient pu s'inspirer d'Haskell (ou de Rust, ou de OCaml, …). Au final, je ne me servirais des std::optional qu'une fois enrobés dans une API plus sécurisée.

    Pour le reste, d'autres ont déjà montrés qu'un pointeur initialisé à null par défaut est potentiellement un comportement indéfini, et dans ce cas tout aussi dangereux qu'un pointeur non initialisé. Mais mon point n'était pas que je conseil de privilégier la non initialisation à l'initialisation par défaut. Je veux juste bannir les deux autant que possible et je pense qu'il est possible de construire des API qui apportent cette sécurité.

    Pour finir, je préfère les valeurs explicites car je pense que cela augmente la lisibilité du code. Et à ce niveau, les constructeurs par défaut ne sont pas explicite dans leur intentions. Encore une fois, quel doit être le constructeur par défaut d'une Matrice 4x4 ? d'une couleur ? d'un répertoire ? d'une image ? Je ne sais pas répondre à ces questions, et c'est pour cela que je préfère des fonctions / méthodes statiques avec des noms explicites que des constructeurs dont le comportement est arbitraire et implicite.

  • [^] # Re: Pas uniquement string

    Posté par  (site web personnel) . En réponse au journal Switch, chaîne constante et c++. Évalué à 1.

    L'exemple flagrant est les pointeurs, notamment pointeurs de fonction. Si tu le garde non-initialisé tu as un énorme trou de sécurité potentiel.

    C'est quoi la valeur par défaut d'un pointeur de fonction qui as du sens ? nullptr, ou une fonction au hasard ? Les deux cas n'ont pas de sens et sont dangereux, bref, avoir un pointeur "default-initialised" n'as pas de sens.

    Ensuite, il n'y a absolument aucun besoin d'avoir un code instable pour aller chercher ces erreurs, n'importe quel compilateur décent te détectera une variable non-initialisée sur demande.

    Justement ;) : les valeurs non-initialisées sont moins dangereuses que les valeurs initialisée par défaut, car souvent les valeurs non initialisée vont te générer une erreur, alors que les valeurs par défaut ne vont pas t'en générer et tu continues sur un bout de code qui n'a plus de sens.

    On parle bien du cas tu as un bug dans ton code et alors que tu pensais gérer tous les cas de figure, il en manque certains et au lieu de mettre une valeur qui a du sens dans ton code, tu finis avec une valeur par défaut qui n'en a pas.

    Gni ? Tu demandes quoi là ? Evidemment que l'auteur de la classe va mettre ce qu'il désire dans le constructeur par défaut, c'est son code ! Tu veux faire quoi à la place ?

    Je ne veux pas de constructeur par défaut ! Exemple, si tu croises le bout de code suivant Color c;, quel est la valeur d'une Color ? Maintenant tu croises Color c = Color::Black(). Cette seconde solution est :

    a) Bien plus facile à lire / comprendre. Il est plus explicite. Il demande aussi moins de surcharge intellectuel au développeur qui n'a pas à retenir la liste des cas par défaut pour toutes les classes.
    b) Il est moins sujet aux changements des comportements par défaut, je détail ce point qui te faisait bondir.

    Imagine une interface de la classe Color comme suit :

    // Color Triple
    class Color
    {
    ....
    public:
        // Default Color (black)
        Color();
    
        // Init the triple with (a, b, c)
        Color(const float a, const float b, const float c);
    };

    Ici mon seul contrat concernant le constructeur par défaut c'est le petit commentaire qui dit "black". C'est faible, et demain ce constructeur par défaut peut changer très facilement pour construire une autre couleur, sans doute le blanc, car pour les besoins de ce projet on fait plus du multiplicatif sur les couleurs que de l'additif, donc le blanc semble un meilleur choix par défaut. Tu peux aussi imaginer un refactoring dans lequel on change la classe de gestion de couleur, la plupart des opérations ont la même sémantique, sauf ? Les choix arbitraire des constructeurs par défaut.

    Une autre conception pourrait donner :

    // Color Triple
    class Color
    {
    ....
    public:
        // Smart constructors
        static Color Black();
        static Color White();
    
        // Init the triple with (a, b, c)
        Color(const float a, const float b, const float c);
    };

    Dans ce cas là, pas de constructeur par défaut, et des constructeurs aux noms bien plus explicites et plus plus robustes, ce n'est pas une valeur par défaut arbitraire, c'est Black ou White, c'est écrit en dur dans le nom de la fonction, donc il faudrait vraiment être pervers pour faire un refactoring dans lequel la nouvelle classe Color renvoie du vert dans sa fonction White.

    En résumé, je préfère l'explicite et j'évite les les valeurs par défauts, les constructeurs par défauts, les arguments par défauts sur les fonction. Tout cela parce que de mon expérience, c'est trop facile de se tromper, et cela coûte trop cher. J'évite aussi les variables non initialisées (ou initialisées par défaut) qui servent de valeur de sortie d'une fonction en passage par référence, parce que c'est encore un coup à se planter. Je veux qu'à tout moment dans mon code, si j'ai accès à une variable, elle ai un sens.

  • [^] # Re: Pas uniquement string

    Posté par  (site web personnel) . En réponse au journal Switch, chaîne constante et c++. Évalué à 0.

    Ma question n'était pas la raison technique qui fait que c'est plutôt des bits à zéro qu'autre chose. Je voulais plutôt discuter du coté totalement arbitraire des initialisation par défaut.

    Les valeur par défaut sont, à mon sens, plus dangereuses que les valeur non-initialisée. Parce que non-initialisée, cela fait souvent n'importe quoi, et dans le meilleur des cas, cela plante, et tu trouves vite le bug et au moins tu obtient un comportement instable qui t'invites à chercher une erreur. Mais lorsque c'est initialisé par défaut, tu vas obtenir un comportement stable, souvent cohérent, mais qui est totalement faux.

    De plus, les valeurs par défaut des types primitifs sont assez évidentes, mais celles de types plus élaborés sont totalement dépendants des choix des auteurs de la classe, et cela rend le code bien moins lisible. Il faut aussi voir que les valeurs par défaut peuvent changer au bon vouloir du l'auteur de la classe, et celui-ci peut penser que son changement est indolore alors qu'en fait il détruit ton code.

  • [^] # Re: Pas uniquement string

    Posté par  (site web personnel) . En réponse au journal Switch, chaîne constante et c++. Évalué à 2.

    En C++, var m; est default-initialized.

    C'est comme dire non initialisé, mais en pire ;)

    Autant cela à un sens plus ou moins arbitraire pour certains types (genre std::vector), mais pourquoi un bool serait plus false que true ou un int mérite-t-il plus d'être 0 que 30. Tant qu'on est dans le code-smell, j'ai tendance à faire des bonds sur les classes qui proposent un constructeur par défaut vide avec des valeurs par défaut. Il faut vraiment justifier que le choix par défaut à du sens.

    Généralement je préfère une fonction static ou free avec un nom explicite pour les cas par défaut.