Partage
  • Partager sur Facebook
  • Partager sur Twitter

Utilisation des pointeurs en C++

    23 juin 2018 à 0:33:19

    Bonjour :)

    J'aurais aimé avoir votre avis sur l'utilisation des pointeurs en C++.

    On conseil très souvent de ne pas avoir les habitudes du C lorsque l'on fait du C++, et il se trouve que les pointeurs en font partie.

    Actuellement, je crée un projet dont une variable évolue tout au long du programme. Elle est déclarée dans la fonction main, et finalement retranscrite dans un fichier à la fin du programme. Pour la modifier, j'utilise plusieurs classes, espaces de noms, fichiers, fonctions, ... Mais je trouve que cela fait un peu lourd d'écrire chaque fois quelque chose comme ceci :

    void main() {
       FinalText text;
       text += MyFunc1(...);
       text += MyFunc2(...);
       text += MyFunc2(...);
    }

    De plus, cette variable contient des méthodes qui doivent parfois être appelées pour ajouter quelques choses au rendu final ou autre.

    Enfin, le contexte on s'en fou un peu, mais voici à quoi mon code ressemble actuellement :

    void main() {
       FinalText text;
       MyFunc1(&text);
       MyFunc2(&text);
       MyFunc3(&text);
    }

    Pour faire appel à des méthodes depuis cette variable pointé, j'utilise un procédé de définition de méthode grâce à typedef :

    MyFunc(FinalText *text) {
       typedef void (FinalText::*MyMethod)(/*arguments types*/);
       (text->MyMethod)(/*arguments*/);
       *text += something;
       ...
    }

    Personnellement, une utilisation "massive" des pointeurs ne me fait pas peur, mais je ne sais pas si c'est une bonne chose, une bonne habitude.

    Quel est votre avis quant à une tel utilisation de pointeurs ?

    Merci, et désoler si la question paraît un peu bête, mais je préfère savoir.

    • Partager sur Facebook
    • Partager sur Twitter
    Le doute est le commencement de la sagesse
      23 juin 2018 à 1:00:08

      Salut,

      Juste une petite question : pourquoi absolument vouloir passer une donnée dont tu sais qu'elle existe obligatoirement par pointeur, au lieu de la passer par référence?

      Cela n'aurait que des avantages:

      • tu aurais la certitude que la donnée existe au moment de l'appel de la fonction
      • la syntaxe pour manipuler la référence serait la même dans la fonction appelée que s'il s'agissait de la donnée elle-même
      • tu n'aurais aucun doute quant à l'éventuel besoin de libérer (ou non) la mémoire allouée au pointeur

      De plus, on le répète à l'envie : on n'utilise les pointeurs que lorsque l'on n'a pas d'autre choix.  Or, l'alternative qui consiste à utiliser des références -- qu'elles soient constantes ou non -- s'avère souvent parfaitement utilisable (surtout depuis C++11 et l'arrivée de std::reference_wrapper )

      Enfin, fais juste attention à un truc tout bête: tu te pleins de la lourdeur d'écriture d'un code proche de text += maFonction(...);, mais l'alternative est de faire en sorte que maFonction occasionne ce que l'on appelle un "effet de bord" sur ta donnée (qu'elle prenne la dite donnée sous la forme d'un pointeur ou d'une référence n'ayant pas beaucoup d'importance sur ce point).

      Or la présence d'un effet de bord dans un code n'a que des inconvénients:

      1- Cela rend le code beaucoup plus difficile à comprendre et à débugger, parce que chaque appel de fonction (et chaque fonction appelée de manière indirecte) risque de modifier la valeur de nos données, et, si ce n'est pas fait correctement, on en arrive rapidement à devoir vérifier toutes les fonctions qui sont appelées pour arriver à déterminer "ou ca cloche".

      2- Cela fout un bordel incroyable lorsque l'on travaille dans un contexte multi-threadé, car il suffit que deux fonctions ayant un effet de bord sur la même donnée soient appelées par deux threads différents pour que le résultat soit imprévisible ;)

      • Partager sur Facebook
      • Partager sur Twitter
      Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
        23 juin 2018 à 1:03:19

        Pourquoi un pointeur et pas une référence ???

        Le "rejet" des pointeurs (rejet relatif, c'est surtout qu'il faut les utiliser uniquement quand ils sont adaptés) en C++ n'est pas une question de goût. Il y a des raisons techniques. Si ton seul argument, c'est que "c'est lourd", c'est bof bof.

        En soit, la seconde syntaxe n'est pas choquante. On pourrait proposer ça par exemple pour modifier des données qui sont dans un buffer (mais pas avoir un pointeur alors que tu peux utiliser une reference). Mais dans ton exemple, je ne vois pas trop de raison de préférer la seconde. (Et même, on pourrait dire que la première syntaxe respecte plus la pureté fonctionnelle).

        Si les pointeurs ne te font pas peur, c'est probablement que tu ne comprends pas les problèmes qu'ils posent. (Et donc c'est encore plus risqué de les utiliser). Si tu les utilises massivement, c'est peut être parce que tu ne connais pas les alternatives et en quoi elles sont mieux. (Donc c'est encore plus un problème)

        • Partager sur Facebook
        • Partager sur Twitter
          23 juin 2018 à 1:07:42

          J'avoue ne pas comprendre comment on est passé d'un appel de fonction à l'utilisation d'un pointeur de fonction membre ?

          Du coup, je ne vais me concentrer que sur le paramètre text. Pourquoi un pointeur ? Et surtout, pourquoi la référence devient magiquement un pointeur lorsqu'elle devient un paramètre ?

          À mon sens, il existe plusieurs situations où le pointeur est "légitime" (ou pas):

          • passage d'ownership d'un objet alloué dynamique. Sauf que cela fait belle lurette qu'il y a std::unique_ptr ou autres pointeur intelligent.
          • utilisation d'un paramètre optionnel. Personnellement je préfère un type explicite comme std::optionnel.
          • le couple data+len, même si un objet dédié tel qu'un array_view serait mieux (std::span en c++20).
          • un référé qui peut changer. Mais un std::reference_wraper ou not_null_ptr est de mise (gsl::not_null).
          • des cas obscurs de manipulation de mémoire.

          Où se situe text là-dedans ? Le paramètre devrait être une référence.

          • Partager sur Facebook
          • Partager sur Twitter
            23 juin 2018 à 11:48:09

            Ok, merci pour vos réponses :)

            Je ne connais pas réellement la différence entre les références et les pointeurs en fait. Raison pour laquelle je ne les utilises probablement pas.

            Je ne me plein pas non-plus vraiment de la "lourdeur" syntaxique du code, mais plutôt du fait de devoir déclarer plusieurs de dizaine de fonctions qui effectueraient "plus de travail" à renvoyer une valeur, puis ensuite de la traiter avec la variable que de traiter directement cette variable.

            Il est également possible que je ne connaisse pas tout des pointeurs (c'est même une certitude), mais je pense en connaitre suffisamment que pour pouvoir les utilisées à bonne escient.

            Quant aux effets de bord, même si le risque n'est pas nul, il l'est quasiment.

            Ma variable à traiter contient plusieurs champs et méthodes qui agissent sur la valeur privée, qui sera par la suite retranscrite dans un fichier. Si j'ai besoin d'ajouter une donnée X ou Y à l'aide d'une méthode de cette même variable par exemple, la méthode se charge de modifier la variable et/ou les conteneurs temporaire privés pour que tout soit bien manipuler lors de la retranscription des données.

            Evidemment, je pourrais me passer des pointeurs, mais je n'ai encore rencontrer aucuns problèmes pour le moment.

            • Partager sur Facebook
            • Partager sur Twitter
            Le doute est le commencement de la sagesse
              23 juin 2018 à 12:48:11

              Geralt de Riv a écrit:

              (1) Il est également possible que je ne connaisse pas tout des pointeurs (c'est même une certitude), mais je pense en connaitre suffisamment que pour pouvoir les utilisées à bonne escient.

              (2) Quant aux effets de bord, même si le risque n'est pas nul, il l'est quasiment.

              (1) Les pointeurs c'est la raison numéro 1 d'undefined behavior dans les programmes C et même les experts du langage se cassent les dents dessus. Alors je pense pouvoir affirmer sans trop m'inquiéter que tu vas juste te tirer une balle dans le pied aussi souvent qu'il sera possible.

              (2) Des effets de bords, tu en fais tout le temps, c'est leur quantité qui peut poser problème et la manière dont ils interagissent.

              Geralt de Riv a écrit:

              Je ne me plein pas non-plus vraiment de la "lourdeur" syntaxique du code, mais plutôt du fait de devoir déclarer plusieurs de dizaine de fonctions qui effectueraient "plus de travail" à renvoyer une valeur, puis ensuite de la traiter avec la variable que de traiter directement cette variable.

              Si tu as besoin de plusieurs dizaines de fonction pour remplacer quelques pointeurs, c'est ta conception qu'il faut que tu revoies. Très clairement.

              Geralt de Riv a écrit:

              Evidemment, je pourrais me passer des pointeurs, mais je n'ai encore rencontrer aucuns problèmes pour le moment.

              Tu as combien de tests unitaires pour chacune de tes fonctions ?

              -
              Edité par Ksass`Peuk 23 juin 2018 à 12:48:46

              • Partager sur Facebook
              • Partager sur Twitter

              Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C

                23 juin 2018 à 13:01:40

                Ksass`Peuk a écrit:

                Si tu as besoin de plusieurs dizaines de fonction pour remplacer quelques pointeurs, c'est ta conception qu'il faut que tu revoies. Très clairement.

                Tu veux dire quoi par là ?

                Ksass`Peuk a écrit:

                Les pointeurs c'est la raison numéro 1 d'undefined behavior dans les programmes C et même les experts du langage se cassent les dents dessus. Alors je pense pouvoir affirmer sans trop m'inquiéter que tu vas juste te tirer une balle dans le pied aussi souvent qu'il sera possible.

                Evidemment je ne suis pas un professionnel... Mais dans simplement ça :

                void main() {
                    std::string MyVar;
                    foo(&MyVar);
                }
                
                void foo(std::string *MyVar) {
                    *MyVar= "Hello";
                }

                Quels pourraient être les problèmes/risques ? Mise à part que les données manipulées par mon programme sont en provenance de l'utilisateur ?

                En gros, mon programme fonctionne comme tel. Avec également des appels de méthodes depuis la variable du type X pointé.

                Ksass`Peuk a écrit:

                Des effets de bords, tu en fais tout le temps, c'est leur quantité qui peut poser problème et la manière dont ils interagissent.

                Oui, excuse moi, je me suis trompé de terme. Eh bien, justement, des fonctions void modifiant une variable pointée, il ne peut y avoir que des effets de bord. Mais, comment cela pourraient-il poser problème à long terme ?

                Ksass`Peuk a écrit:

                Tu as combien de tests unitaires pour chacune de tes fonctions ?

                Zéro. J'ai testé beaucoup de possibilités, et il s'avère que cela fonctionne à chaque fois. Et je sais, ce n'est surement pas bon...

                -
                Edité par Geralt de Riv 23 juin 2018 à 14:03:39

                • Partager sur Facebook
                • Partager sur Twitter
                Le doute est le commencement de la sagesse
                  23 juin 2018 à 13:37:50

                  Lu',

                  Geralt de Riv a écrit:

                  Evidemment je ne suis pas un professionnel... Mais dans simplement ça :

                  void main() {
                      std::string MyVar;
                      foo(&MyVar);
                  }
                  
                  void foo(std::string *MyVar) {
                      *foo = "Hello";
                  }

                  Quels pourraient être les problèmes/risques ?


                  Moi, on m'a toujours dit qu'avant de dereferencer un pointeur, il faut systematique un "assert" avant.

                  Un des risques:

                  void foo(std::string *MyVar) {
                      *MyVar = "Hello";
                  }
                  
                  int main() {
                      std::string *s;
                      foo(s); //segfault
                      return 0;
                  }



                  • Partager sur Facebook
                  • Partager sur Twitter

                  Eug

                    23 juin 2018 à 13:56:38

                    Geralt de Riv a écrit:

                    Evidemment je ne suis pas un professionnel... Mais dans simplement ça :

                    void main() {
                        std::string MyVar;
                        foo(&MyVar);
                    }
                    
                    void foo(std::string *MyVar) {
                        *foo = "Hello";
                    }

                    Quels pourraient être les problèmes/risques ? Mise à part que les données manipulées par mon programme sont en provenance de l'utilisateur ?

                    Bien sur, si on fait toujours très attention avec les pointeurs et qu'on ne se trompe jamais, il n'y aura pas d'accidents. Mais on se gourre si facilement : la preuve c'est que tu essaies d'affecter *foo, au lieu de *MyVar...

                    D'autant que le choix de MyVar comme nom n'est pas terrible. C'est un pointeur, une variable contenant l'adresse d'une chaine, faudrait quelle s'appelle adr_myvar, un truc comme ça, sinon on est en train de se faire des noeuds par métonymie, en appelant variable ce qui est son adresse. Les conditions idéales pour se gourrer, on se les construit soi-même, souvent.

                    void bar() 
                    {
                        std::string maChaine;
                        foo( & maChaine);
                    }
                    
                    void foo(std::string * adr_chaine) {
                        * adr_chaine = "hello";
                    }
                    


                    Heureusement le langage donne un moyen de faire plus simple :

                    void bar() {
                        std::string maChaine;
                        foo(maChaine);
                    }
                    
                    void foo(std::string & chaine) {
                        chaine = "Hello";
                    }


                    Le paramètre est une chaine, qui est modifiée par la fonction. Et basta. Pas besoin d'y mêler les notions d'adresse/pointeur/déréférencement.

                    Quand on fait simple, sans ajouter des concepts inutiles, ça libère des neurones, qu'on peut utiliser pour mieux vérifier ce qu'on écrit.

                    -
                    Edité par michelbillaud 23 juin 2018 à 14:04:14

                    • Partager sur Facebook
                    • Partager sur Twitter
                      23 juin 2018 à 14:03:14

                      Oups :honte: J'ai été trop vite pour écrire mon exemple. Je corrige ça tout de suite.

                      Mais ce code n'est qu'un exemple. Je n'utiliserais jamais de nom tel que foo ou MyVar, c'était juste pour illustrer...

                      • Partager sur Facebook
                      • Partager sur Twitter
                      Le doute est le commencement de la sagesse
                        23 juin 2018 à 16:28:19

                        Salut,

                        L'un des apprentissages le plus difficile (après celui de se faire un idée précise de ce que sont les pointeurs et de tous les problèmes qu'ils peuvent occasionner, justement) est d'arriver à se convaincre de faire comme si on ne savait rien du contexte dans lequel la fonctionnalité que l'on développe est utilisée.

                        Je m'explique:

                        Mettons que tu crées une fonction foo.  Tu le fait sans doute soit pour respecter le SRP (et donc la règle "un verbe == une fonction"), soit parce que tu t'es rendu compte que tu dois "factoriser" du code afin de respecter le DRY.

                        A priori, tu as donc au moins une vague idée d'au moins un contexte dans lequel cette fonction sera appelée: que la variable transmise comme paramètre aura été déclarée de telle ou telle manière, qu'elle a déjà subit telle ou telle transformation avant l'appel de la fonction que tu développes, et -- peut-être même -- qu'elle subira telle ou telle transformation après l'appel à la fonction que tu développes.

                        Hé bien, tu dois te convaincre d'ignorer purement et simplement ce contexte, parce que, si ça se trouve, dans les semaines et les mois à venir, tu fera appel à cette fonction dans dix ou vingt situations totalement différentes du contexte dans lequel tu envisages d'y faire appel aujourd'hui.

                        Or, une fois que l'on est à l'intérieur de la fonction, tout ce que la fonction sait, c'est :

                        • les données qu'elle a reçues en paramètre
                        • les variables locales qu'elle va déclarer
                        • la logique qu'elle doit suivre
                        • les éventuelles fonctions auxquelles elle devra faire appel (en partant du principe qu'elles feront correctement leur boulot), et donc
                          • les données qu'elle devra leur transmettre et sous quelle forme et
                          • le type de donnée qu'elle doit obtenir lors de l'appel
                        • la valeur qu'elle devra (éventuellement) renvoyer

                         Mais, par contre, la fonction est totalement incapable de "tracer l'origine" des données qu'elle reçoit en paramètre: elle n'a aucune idée du contexte dans lequel elle a été appelée, et elle ne peut donc absolument pas "partir du principe" que tel paramètre a été déclaré de telle ou telle manière ou qu'il a déjà subit telle ou telle modification.

                        Tout ce qu'elle peut éventuellement faire, c'est comparer la valeur de la donnée qu'elle a reçu en paramètre à l'une ou à l'autre valeur "prédéfinie" afin de décider la manière dont ell va travailler. 

                        Mais l'usage cette possibilité devrait être réduit au maximum, car cela rend la logique à mettre en place tout de suite beaucoup plus compliquée.

                        Ensuite, vient le temps de se poser la question dont la réponse fâche : est-ce que la fonction peut faire confiance à celui qui y fera appel pour qu'il transmette des données valides et cohérentes?

                        Malheureusement, la réponse à cette question est toujours non, car, en vertu de la loi de l'emmerdement maximum, l'utilisateur (comprend: celui qui fera appel à la fonction) est un imbécile distrait.  Et le fait que l'utilisateur (présumé) de la fonction soit -- justement -- celui qui l'aura développée n'y change absolument rien :p

                        La fonction doit donc systématiquement mettre en doute toutes les données qu'elle obtient par n'importe quel moyen (que ce soit sous la forme de paramètre, en la demandant à l'utilisateur de l'application ou en l'extrayant d'un flux de données quelconques) et dont elle ne définit pas elle-même la valeur.

                        • Partager sur Facebook
                        • Partager sur Twitter
                        Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                          23 juin 2018 à 17:41:00

                          Geralt de Riv a écrit:

                          J'ai été trop vite pour écrire mon exemple. Je corrige ça tout de suite.

                          Tu viens de montrer quel est le problème : tu feras des erreurs. Et avec les pointeurs, les erreurs sont tres compliquées a debuger.

                          Bien sur que c'est un code d'exemple et que pour un vrai code, tu aurais fait plus attention. Mais pour un vrai code, tu écriras plus de code, tu n'auras pas un relecteur qui te signalera l'erreur. Et quelque soit l'attention que tu portes a ton code, tu feras toujours des fautes.

                          Les évolutions des langages (pas que le C++, tous les langages) et des techniques de programmation ne sont qu'une réponse au fait qu'on essaie d'éviter de refaire les mêmes erreurs. 

                          • Partager sur Facebook
                          • Partager sur Twitter
                            23 juin 2018 à 18:49:08

                            Geralt de Riv a écrit:

                            Quels pourraient être les problèmes/risques ? Mise à part que les données manipulées par mon programme sont en provenance de l'utilisateur ?

                            A long terme?

                            Tu vas avoir une fonction (foo) qui a un effet de bord qui appelle une deuxième fonction (bar) qui a un autre effet de bord qui elle-même appelle deux fonctions encore différentes (doIt et doSomething) (*) qui ont elles aussi un effet de bord (différent de tous les autres, cela va de soi), et tu vas te rendre compte que foo ne fournit pas le résultat auquel tu t'attendais.

                            Et pour pouvoir corriger le problème afin d'obtenir le résultat attendu, tu vas devoir commencer par... localiser l'origine de ce problème.

                            Mais pour localiser l'origine du problème, tu n'auras que l'embarras du choix car le problème peut trouver son origine:

                            • au niveau de la logique de ta fonction foo
                            • au niveau de l'effet de bord occasionné par foo
                            • au niveau de l'effet de bord occasionné par bar
                            • au niveau de l'effet de bord occasionné par doIt
                            • au niveau de l'effet de bord occasionné par doSomething

                            Mais comment pourras tu en être sur?  La seule solution que tu auras sera de  vérifier systématiquement le résultat de toutes de foo, de bar, de doIt et de doSomething pour t'assurer que les modifications apportées sont cohérentes.

                            A l'inverse, si aucune de ces fonctions n'occasionnent un effet de bord, mais qu'elles renvoient systématiquement une valeur correspondant au résultat qu'elles ont obtenu lors de leur traitement, les choses deviennent bien plus simples:

                            tu vérifie le résultat obtenu lors de l'appel de chaque fonction.  S'il correspond à ce à quoi tu t'attendais, c'est que cette fonction fait correctement son taf.  Et s'il ne correspond pas à ce à quoi tu t'attendais, tu vas voir dans la fonction ce qui se passe, pourquoi elle ne donne pas le résultat auquel tu t'attendais.

                            Cette "nouvelle" fonction que tu observe fera peut-être -- elle aussi -- appel à d'autres fonctions, qui renverront -- elles aussi -- le résultat de leur traitement.  Et encore une fois, tu pourras vérifier que le résultat de leur traitement correspond au résultat auquel tu t'attendais.  Et ainsi de suite.

                            A chaque fois que tu décideras d'aller voir ce que fait une fonction qui ne renvoie pas le résultat auquel tu t'attendais, tu te rapprochera "un peu plus" de l'origine réelle de ton problème, jusqu'à ce que, à un moment donné, tu tombes sur "la connerie qui fait tout foirer": une comparaison foireuse, une boucle qui ne s'exécute pas ou l'ajout d'un 's' en trop dans une chaine de caractères.

                            Bien sur, la connerie en question peut être un peu plus subtile que les cas que j'ai cités.  Mais l'idée reste malgré tout la même.

                            Le gros avantage, c'est qu'au lieu de devoir parcourir toutes les fonctions qui sont appelées pour t'assurer qu'elles font correctement leur taf, tu pourras systématiquement écarter du problème celles dont le résultat est correct, pour n'avoir à t'occuper que de celles pour lesquelles ce n'est pas le cas.

                            Sur un projet important, cela peut faire passer le temps de débugage de plusieurs heures à... quelques minutes à peine ;)

                            (*) Et je pourrais continuer longtemps comme cela, avoir plusieurs dizaines de fonctions appelées au final par une seule fonction à la base est monnaie courante ;)

                            -
                            Edité par koala01 23 juin 2018 à 18:51:12

                            • Partager sur Facebook
                            • Partager sur Twitter
                            Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                              23 juin 2018 à 19:51:23

                              Geralt de Riv a écrit:

                              Oups :honte: J'ai été trop vite pour écrire mon exemple. Je corrige ça tout de suite.

                              Mais ce code n'est qu'un exemple. Je n'utiliserais jamais de nom tel que foo ou MyVar, c'était juste pour illustrer...


                              Je vois bien. Je profitais de l'exemple pour souligner, c'est qu'il y avait deux trucs, en apparence innocents, qui contribuent  à s'embrouiller. Classiquement

                              void foo(struct Thing *thing) {      
                                ...
                              }

                              c'est fait avec de la bonne volonté (c'est mieux que *t) mais il faut garder obstinément dans un coin de la tête, pour lire le code qui suit le fait que "thing" n'est pas une structure Thing, mais un pointeur.

                              Le second c'est qu'en travaillant avec des pointeurs plutot qu'avec des références, il faut constamment faire attention entre le pointeur, qu'on manipule explicitement, et la chose pointée.


                              Mine de rien, des neurones, y en a pas tant que ça, et ils fatiguent vite. Dès qu'on charge un peu, le taux d'erreurs grimpe, ce qui donne du boulot en plus. Et ça, c'est mal.

                              -
                              Edité par michelbillaud 23 juin 2018 à 19:53:37

                              • Partager sur Facebook
                              • Partager sur Twitter
                                23 juin 2018 à 20:42:42

                                Merci pour toutes vos réponses, je vois mieux comment m'améliorer :)

                                Je sais que je vais paraître un peu têtu, mais je suis quand même pratiquement sûr du résultat qu’occasionnerons mes fonctions void qui modifient ma valeur pointée (encore une fois, sans testes unitaire...)

                                En effet, ce n'est pas "bêtement" modifier un champ std::string privé à tout bout de champs. Chaque fonction void est appelée si et seulement si le programme détermine qu'un tel ajout doit être fait (il n'y a que des ajouts, aucunes de modifications des valeurs précédentes). En prime, lors de la retranscription grâce à une méthode MyVar.GetFinal(), un "brassage" est effectué entre les champs privés afin d'avoir une sortie optimal.

                                Par exemple si l'utilisateur utilise plusieurs fois une fonctionnalité externe à inclure, le programme ne perd pas son temps à ré-importer la fonctionnalité, puisqu'elle à déjà été utilisée. Il ne boucle donc pas bêtement.

                                Les valeurs pointées sont utilisées par les fonctions uniquement si le programme leur en "donne la permission". Et même si il y a une petite coquille (ce qui n'est pas censé arrivé avec tous les testes, mais on est jamais trop prudent), la méthode GetFinal() s'assure de la corriger intelligemment selon les précédentes valeurs.

                                Autre exemple, si une fonction à besoin de faire appel à une des méthodes de la variable pointée, cette même méthode s'assure de la viabilité des données ajoutées afin d'éventuellement les corriger avant de les intégrées, si besoin.

                                Je pense faire assez attention à l'utilisation des pointeurs dans ce projet en fait. Mais, évidemment, cela ne veut pas dire qu'il n'y aucuns risque d'erreur.

                                Par contre, pourquoi une fonction devrait-elle "savoir" quelles ont étés les précédentes modifications de la variable pointé qu'elle traite ?

                                -
                                Edité par Geralt de Riv 23 juin 2018 à 20:44:14

                                • Partager sur Facebook
                                • Partager sur Twitter
                                Le doute est le commencement de la sagesse
                                  23 juin 2018 à 20:54:10

                                  Alors, utilise des références (constantes!!! histoire de bien indiquer qu'elles ne seront pas modifiées) comme paramètre.  Tu verras, tu ne t'en sentira que mieux dans ton développement ;)

                                  Et puis

                                  Geralt de Riv a écrit:

                                  mais je suis quand même pratiquement sûr du résultat qu’occasionnerons mes fonctions void qui modifient ma valeur pointée (encore une fois, sans testes unitaire...)

                                  Traduction: tu n'es sur de RIEN: Tu crois, tu espères que tes fonctions n'auront pas d'effets de bord.

                                  Mais comme dirait mon père: laisse croire les béguines, elles sont spécialistes et payées pour!

                                  Toi, en tant que développeur, tu dois avoir des certitudes.  Tu dois même dans certains cas (Ksass ` Peuk pourrait t'en parler :D) pouvoir apporter la preuve de tout ce que tu avances.

                                  Le problème, c'est que le développeur informatique est soumis à la même règle que les ambulanciers:

                                  on ne peut être sur que d'une chose : on n'est jamais sur de rien (et même cela, il est permis d'en douter :p )

                                  -
                                  Edité par koala01 23 juin 2018 à 21:02:28

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                  Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                                    23 juin 2018 à 22:57:28

                                    J'aime bien tes petites notes philosophique éducative :)

                                    koala01 a écrit:

                                    Alors, utilise des références (constantes!!! histoire de bien indiquer qu'elles ne seront pas modifiées) comme paramètre.  Tu verras, tu ne t'en sentira que mieux dans ton développement ;)

                                    Ok, j'y tâcherais :)

                                    koala01 a écrit:

                                    Traduction: tu n'es sur de RIEN: Tu crois, tu espères que tes fonctions n'auront pas d'effets de bord.

                                    Correction: Je suis sûr des testes que j'ai effectuées, mais j'escompte et désire qu'il n'y aura pas d'effets de bord indésirable lorsque ça sera au tour de l'utilisateur.

                                    koala01 a écrit:

                                    Toi, en tant que développeur, tu dois avoir des certitudes.  Tu dois même dans certains cas (Ksass ` Peuk pourrait t'en parler :D) pouvoir apporter la preuve de tout ce que tu avances.

                                    Oui, j'ai jeter un p'tit coup d’œil au cours sur la preuve de programmes C de Ksass`Peuk. J'ai également suivi une partie du talk d'hier (jeudi), vers 20h00. Ce que je retiens de tout ça, c'est qu'il faut se fier aux outils extérieur et avoir un contrôle maximum sur toutes les éventualités. Je ferrais donc des testes unitaire (ce sera une "première" pour moi ^^ ).

                                    koala01 a écrit:

                                    Le problème, c'est que le développeur informatique est soumis à la même règle que les ambulanciers:

                                    on ne peut être sur que d'une chose : on n'est jamais sur de rien (et même cela, il est permis d'en douter :p )

                                    En effet, le doute est le commencement de la sagesse ;)
                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                    Le doute est le commencement de la sagesse
                                      23 juin 2018 à 23:44:21

                                      Geralt de Riv a écrit:

                                      J'aime bien tes petites notes philosophique éducative :)

                                      A laquelle fais tu référence? A

                                      koala01 a écrit:

                                      laisse croire les béguines, elles sont spécialistes et payées pour!

                                      ou à

                                      koala01 a écrit:

                                      on ne peut être sur que d'une chose : on n'est jamais sur de rien (et même cela, il est permis d'en douter :p )

                                      Pour la première, remercie mon père.  Un homme d'une grande sagesse qui n'a pas su s'empêcher de faire quelques conneries malgré tout :D

                                      Pour la deuxième... Ca aide d'avoir servi dans un service d'ambulances pendant 8 ans :D


                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                      Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                                        23 juin 2018 à 23:53:27

                                        Je parlais de l'ensemble :)

                                        Donc je remercie ton père et le service d'ambulance :p

                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                        Le doute est le commencement de la sagesse
                                          25 juin 2018 à 11:27:52

                                          Geralt de Riv a écrit:


                                          Correction: Je suis sûr des testes que j'ai effectuées, mais j'escompte et désire qu'il n'y aura pas d'effets de bord indésirable lorsque ça sera au tour de l'utilisateur.

                                          Dans ce cas, mon conseil est de te rendre sans attendre dans l'église la plus proche de chez toi d'y brûler une bonne centaine de cierges et de rester là bas prier pendant quelques semaines. On ne sait jamais, il y a peut être un ange qui fera en sorte que ton programme fonctionne correctement...

                                          Plus sérieusement, Murphy veille au grain, s'il y a la moindre chance pour que ton programme merde, tu peux être certain qu'il va merder. Espérer et désirer ne sert à rien ici, ce qu'il faut c'est être sûr que tout à été fait pour qu'il n'y ait pas de problèmes.

                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                          Mettre à jour le MinGW Gcc sur Code::Blocks. Du code qui n'existe pas ne contient pas de bug
                                            25 juin 2018 à 11:44:50

                                            Faut quand même insister sur le fait que la programmation, c'est passer une grande partie de son temps à chercher pourquoi ce qu'on a écrit ne marche pas tout à fait comme on avait prévu. C'est à dire ne marche pas, tout court.

                                            Non seulement ce temps perdu se réduit quand on prend le maximum de précautions, mais c'est aussi le cas pour le temps total = mise en place des précautions + temps perdu.

                                            -
                                            Edité par michelbillaud 25 juin 2018 à 11:46:15

                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              25 juin 2018 à 17:59:40

                                              Je n'ai pas dit non plus que je m'en foutait si aucuns réels tests n'était fait ;) Je vais bien sûr tester mes fonctions, et mon programme en général avec des outils adaptés.

                                              Mais, en attendant que mes fonctions soient terminées, et que je soit sûr de ne plus y toucher, alors là je ferrais des tests pour m'assurer de leurs viabilités.

                                              Au passage, que me conseilleriez-vous pour bien tester un programme / une procédure ?

                                              • Partager sur Facebook
                                              • Partager sur Twitter
                                              Le doute est le commencement de la sagesse
                                                25 juin 2018 à 19:22:20

                                                Préparer plein de tests automatisés.

                                                Et il est en général très profitable d'écrire les tests avant de développer les fonctions.

                                                1) parce que ça permet de voir de suite si ça marche

                                                2) parce que quand on pense que ça marche, la dernière chose qu'on a envie de faire, c'est d'écrire des tests, alors qu'il y a plein de choses plus intéressantes à faire dans la vie.

                                                Donc, puisqu'on en a besoin, le faire avant.

                                                voir google "'tests unitaires", "développement dirigé par les tests", etc.

                                                Au départ, ça peut prendre la forme d'une série d'appels

                                                void test_myStrlen(char * string, int expectedLength) {
                                                   println("- test myStrlen(\"%s\") == %s\n",
                                                           string,  expectedLenght)
                                                   int r = myStrlen(string);
                                                   if (r != exectedLength) {
                                                      println("* FAILED: found %d\n", r);
                                                   }
                                                }
                                                
                                                void serie_tests_myStrlen() [
                                                   test_myStrlen("", 0);
                                                   test_myStrlen("a", 1);
                                                   test_myStrlen("b", 1);
                                                   test_myStrlen("aaaaa", 5);
                                                   test_myStrlne("abcdefgh", 8);
                                                }
                                                


                                                Après, c'est mieux si ça va piocher les valeurs à tester dans un fichier texte.

                                                -
                                                Edité par michelbillaud 25 juin 2018 à 19:25:14

                                                • Partager sur Facebook
                                                • Partager sur Twitter
                                                  25 juin 2018 à 21:17:51

                                                  michelbillaud a écrit:

                                                  Au départ, ça peut prendre la forme d'une série d'appels : ...

                                                  Si c'est ça que l'on appelle "test unitaire", alors aucun problème :) J'ai effectivement écrit une fonction par fonctions à tester qui se charge de vérifier si toutes les possibilités que j'aurais pu imaginer pour une certaine procédure ou fonction travaillent correctement. Depuis un fichier en plus.

                                                  Actuellement, ces tests se sont montrés plutôt concluent, mais j'imagine qu'il y a plus que cela à faire pour être de plus en plus certain du résultat.

                                                  Je pensais que "faire des tests  unitaires" nécessitait des programmes ou logicielle externe en fait. Si ce n'est pas le cas, ou du moins de ce que j'ai compris maintenant, les teste, je les effectue bien :) Je ne "code pas dans le vide" :p .

                                                  Merci pour tout en tout cas :)

                                                  -
                                                  Edité par Geralt de Riv 25 juin 2018 à 21:19:13

                                                  • Partager sur Facebook
                                                  • Partager sur Twitter
                                                  Le doute est le commencement de la sagesse
                                                    25 juin 2018 à 22:01:18

                                                    Dans tes tests unitaires, tu dois aussi tester les cas où quelque chose ne va pas. Par exemple, si tu as un pointeur en paramètre, tu dois vérifier que ta fonction ne fait pas n'importe quoi si on lui donne NULL (avec une référence si c'est possible, tu n'as pas ce problème ^^). Tu ne dois pas seulement tester les possibilités qui arriveront peut-être dans le programme, mais aussi les possibilités qui n'arriveront pas selon toi (même si tu es sûr que tu ne l'appelleras jamais avec NULL, ben une erreur est si vite arrivée).

                                                    • Partager sur Facebook
                                                    • Partager sur Twitter
                                                    Tutoriel Ruby - Bon tutoriel C - Tutoriel SDL 2 - Python avancé - Faîtes un zeste, devenez des zesteurs
                                                      25 juin 2018 à 22:38:21

                                                      Tu sais, quand je faisais mes études, j'avais un prof de C (et par la suite de C++) qui se foutais pas mal du code qu'on lui donnait:

                                                      Il le compilait en activant tous les avertissement qu'il pouvait avec le compilateur, et il commençais par tester les cas pour lesquels le compilateur avait émis un avertissement.

                                                      Par exemple, le compilateur disait quelque chose au sujet d'un problème suite à la comparaison d'une valeur signée avec une valeur non signée? il testait le fonctionnement du programme avec la valeur -1 et la valeur 4 294 967 295

                                                      Et bien sur, il ne se gênait ni pour introduire "hello" ou 3.1415926 quand l'application lui demandait un nombre entier ni pour écrire 32 quand l'application attendait une chaine de caractères.

                                                      Ta note finale dépendait des avertissements que le compilateur avait émis et, surtout, du résultat des tests qu'il avait effectué suite à ceux-ci ;)

                                                      Faire des tests unitaires, c'est cela : vérifier tous les cas qui pourraient "mal tourner" et vérifier que l'application reste cohérente.  C'est rarement en une seule fonction que l'on peut s'en rendre compte ;)

                                                      • Partager sur Facebook
                                                      • Partager sur Twitter
                                                      Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                                                        25 juin 2018 à 23:23:48

                                                        Très bien, merci pour vos conseils :)

                                                        Je tâcherais donc faire souffrir mon programme afin qu'il me révèle chacun de ses moindres défauts :pirate:

                                                        • Partager sur Facebook
                                                        • Partager sur Twitter
                                                        Le doute est le commencement de la sagesse
                                                          26 juin 2018 à 8:07:16

                                                          Enfin bon, il y a différents niveaux de tests (wikpedia)

                                                          << Le Comité français du test logiciel (CFTL) identifie quatre niveaux de test :

                                                          1. Test unitaire (ou test de composants).
                                                          2. Test d'intégration (anciennement test technique ou test d'intégration technique).
                                                          3. Test système (anciennement test fonctionnel ou test d'intégration fonctionnel ou homologation).
                                                          4. Test d'acceptation (anciennement test usine ou recette).

                                                          >>

                                                          le test _unitaire_, c'est un test qui porte sur une partie précise, isolée.

                                                          << Il s'agit pour le programmeur de tester un module, indépendamment du reste du programme, ceci afin de s'assurer qu'il répond aux spécifications fonctionnelles et qu'il fonctionne correctement en toutes circonstances. >> '

                                                          Le prof qui fait tourner un programme en lui refilant des données pourries, ça relève plutot du test d'intégration.

                                                          << dans le test d’intégration, chacun des modules indépendants du logiciel est assemblé et testé dans l’ensemble. >>

                                                          en "boite blanche" (on se base sur les messages d'avertissements et un coup d'oeil au code).   Qui plus est ce n'est pas automatisé.

                                                          PS  Le test est là pour vérifier que la fonction se comporte correctement avec des données qui respectent une certaine spécification.  Si on prend l'exemple d'une implémentation maison de strlen, avec un pointeur de caractères en paramètre, vérifier que ce pointeur n'est pas nul est

                                                          1) hors sujet parce que la spéc dit que le paramètre pointe sur une chaine, donc n'est pas NULL  - faut rester dans le "domaine de définition"

                                                          2) contre-productif (ce n'est pas son job de vérifier, donc le test doit être fait ailleurs ) 

                                                          3) fantaisiste, parce que rien ne dit dans la spécification ce que doit être l'effet dans ce cas, et qu'on vérifie ou pas ça reste un "comportement non spécifié".

                                                          DESCRIPTION
                                                                 The strlen() function calculates the length of the string pointed to by
                                                                 s, excluding the terminating null byte ('\0').
                                                          
                                                          RETURN VALUE
                                                                 The strlen() function returns the number of characters  in  the  string
                                                                 pointed to by s.
                                                          
                                                          

                                                          Le but du prof qui joue à ça est plutot pédagogique : convaincre ses étudiants que les avertissements produits par le compilateur correspondent le plus souvent à des problèmes potentiels, qu'on peut déclencher en réfléchissant un peu. Faut lutter contre "ça compile donc c'est bon".



                                                          -
                                                          Edité par michelbillaud 26 juin 2018 à 8:21:32

                                                          • Partager sur Facebook
                                                          • Partager sur Twitter
                                                            26 juin 2018 à 9:34:32

                                                            Ok, merci pour les compléments, j'irais voir tout ça !

                                                            michelbillaud a écrit:

                                                            ce n'est pas son job de vérifier, donc le test doit être fait ailleurs

                                                            Ha bon ? Mais si on fait quelque chose comme ça :

                                                            int MyStrlen(char *ptr_str) {
                                                                if (ptr_str == NULL) return -1;
                                                                const char *tmp;
                                                                for (tmp = ptr_str; *tmp; ++tmp);
                                                                return(tmp - ptr_str);
                                                            }

                                                            Ce n'est pas "bon" ?

                                                            Faudrait-il alors écrire quelque chose comme ceci :

                                                            bool IsNullChar(char *test) {
                                                                return (test == NULL);
                                                            }
                                                            
                                                            int MyStrlen(char *ptr_str) {
                                                                if (IsNullChar(ptr_str)) return -1;
                                                                const char *tmp;
                                                                for (tmp = ptr_str; *tmp; ++tmp);
                                                                return(tmp - ptr_str);
                                                            }
                                                            

                                                            michelbillaud a écrit:

                                                            "ça compile donc c'est bon".

                                                            Ca compile, c'est déjà ça ^^

                                                            -
                                                            Edité par Geralt de Riv 26 juin 2018 à 9:44:30

                                                            • Partager sur Facebook
                                                            • Partager sur Twitter
                                                            Le doute est le commencement de la sagesse
                                                              26 juin 2018 à 10:08:20

                                                              Si tu reçois une chaîne invalide dans strlen c'est une rupture du contrat, donc c'est pas un scénario d'erreur du programme, c'est un scénario d'erreur du développeur.

                                                              • Partager sur Facebook
                                                              • Partager sur Twitter

                                                              Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C

                                                              Utilisation des pointeurs en C++

                                                              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                                              × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                                              • Editeur
                                                              • Markdown