Partage
  • Partager sur Facebook
  • Partager sur Twitter

Surcharger un opérateur : rendre un attribut const

Formuler le constructeur, pour qu'un attribut soit inviolable

Sujet résolu
    18 juin 2019 à 23:03:40

    Bonjour à tous,

    C'est mon tout 1er post, je vais faire de mon mieux pour le rendre lisible & veuillez m'excusez par avancepour les erreurs de débutant.

    Je début tout juste en algorythmique, j'ai démarré le C il y a 1mois 1/2, & le C++ il y a ~2semaines. Je dois faire tout mon possible pour apprendre les bases avant la rentrée.

    Voici mon problème : 

    Je suis au chapître "Surchargez un opérateur" de la partie 2 du cours C++.

    Je voudrais avoir un constructeur "Duree" qui rend un de ses attributs inviolable, après qu'une fonction opérateur, ou une méthode soit passé dessus (Pour être précis, les 3 opérateurs modifiables sont les int pour heures/minutes/secondes, & je voudrais le string de nom  inchangeable).

    Duree Duree::calculeAddition(Duree const &a, Duree const &b)
    {
      Duree resultat(0, 0, 0);
    
      resultat.m_secondes = a.m_secondes + b.m_secondes;
      if (resultat.m_secondes >= 60){
        resultat.m_minutes += resultat.m_secondes / 60;
        resultat.m_secondes %= 60;}
      resultat.m_minutes += a.m_minutes + b.m_minutes;
      if (resultat.m_minutes >= 60){
          resultat.m_heures += resultat.m_minutes / 60;
          resultat.m_minutes %= 60;}
      resultat.m_heures += a.m_heures + b.m_heures;
    
      return resultat;
    }
    

    ça c'est ma méthode pour les additions

    en dessous je vous mets la brève fonction d'operator qui l'utilise

    Duree operator+(Duree const &a, Duree const &b)
    {
      Duree resultat;
      return resultat = resultat.calculeAddition(a, b);
    }
    

    mes constructeurs :

    Duree();
    Duree(std::string nom, int heures = 0, int minutes = 0, int secondes = 0);
    Duree(int heures, int minutes, int secondes);

    & l'appel que j'en fait depuis le main():

    int main()
    {
    duree1.afficher();
    duree2.afficher();
    Duree dureeResultat("DureeResultat");
    dureeResultat.afficher();
    dureeResultat = duree1 + duree2;
    dureeResultat.afficher(); }
    


    & ce que je récupère dans la console à l'affichage :

    Duree 1 : 4h51m37s
    Duree 2 : 2h11m45s 
    
    DureeResultat : 0h0m0s
    
     : 7h3m22s
    
    

    Je voudrais simplement trouver un moyen pour que l'appel de l'opérateur+ qui lui appelle la méthode "calculeAddition" ne vienne pas écraser l'attribut m_nom de mon objet "dureeResultat".

    Il ne me semble pas que le cours, jusque là (donc juste avant le TD Zfraction) ne propose de moyen de s'en sortir.

    Merci par avance pour le temps passé à lire ma question 


    -
    Edité par Nektar_ia 18 juin 2019 à 23:15:37

    • Partager sur Facebook
    • Partager sur Twitter
      18 juin 2019 à 23:09:01

      Lu',

      LaurentAkA"Nektar" a écrit:

      Voici mon problème : 

      Je suis au chapître "Surchargez un opérateur" de la partie 2 du cours C++.

      Il manque la question...

      • Partager sur Facebook
      • Partager sur Twitter

      Eug

        18 juin 2019 à 23:16:20

        eugchriss a écrit:

        Lu',

        LaurentAkA"Nektar" a écrit:

        Voici mon problème : 

        Je suis au chapître "Surchargez un opérateur" de la partie 2 du cours C++.

        Il manque la question...

        Désolé, mes doigts ont dérapé en pleine rédaction TT

        • Partager sur Facebook
        • Partager sur Twitter
          19 juin 2019 à 3:52:56

          > Je dois faire tout mon possible pour apprendre les bases avant la rentrée.
          Quelle drôle d'idée. Une formation est là pour nous former, pas pour attendre à ce qu'on le soit.

          Sinon, ce chapitre de ce cours est un de ceux qui soulève le plus de questions. Et ici, l'exemple choisi est particulièrement maladroit. Jamais on n'implémentera une paire de classes date et durée avec 3 champs. Ce n'est pas viable.

          Une écriture typique sera:

          struct Duree {
              explicit Duree(int d) : m_duree(d) {}
          
              Duree(int h, int m, int s)
              : Duree(((h*60)+m)*60+s)
              {}
          
              Duree& operator+=(Duree const& d) { 
                  m_duree += d
                  return *this;
              }
              friend duree operator+(Duree lhs, Duree const& rhs) // le "const&" ne rime à rien avec un seul attribut int en vrai
              { return lhs += rhs; }
          
              // noms ambigus
              int heures() const { return m_duree / 3600; }
              int minutes() const { return (m_duree / 60) % 60; } // à vérifier
              int secondes() const { return m_duree % 60; }
          
              friend std::ostream& operator<<(std::ostream &os, Duree d) {
                 return os << d.heures() << 'h' << d.minutes() << 'm' << d.secondes() << 's';
              }
          
          private:
              int m_duree; // pas le temps de réfléchir plus au type le plus adapté
          };

          On pourrait rajouter des constexpr, que tu ne connais pas encore, pour s'autoriser des optimisations et des facilités d'écritures associées, mais dans l'ensemble, on ne peut pas changer n'importe comment les données internes sans passer par un constructeur ou des fonctions privilégiées participant au respect des invariants (inexistants ici, mais c'est un détail) comme l'opérateur +=

          -
          Edité par lmghs 19 juin 2019 à 12:02:37

          • Partager sur Facebook
          • Partager sur Twitter
          C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
            19 juin 2019 à 4:58:10

            Merci pour ta réponse

            Il y a en effet quelques notions dans ton code que je n'ai pas encore abordé (friend, *this, explicit), mais je pense avoir compris l'idée.... que le cours de Mathieu nous fout la tête plus à l'envers qu'autre chose :'( (j'ai commencé le cours de guillaume belz aujourd'hui suite aux conseils lus sur ce forum... peut-être serait-il mieux que j'étudie exclusivement ce dernier)

            Dans mon exemple, le soucis avec l'opérateur +=, dans l'exemple a+=b, le resultat conserve l'attribut m_nom de l'objet "a". Je cherchais à trouver un moyen, de pouvoir garder l'attribut "nom" de d'un objet "c" dans le contexte "c = a + b". Mon problème étant que dans mon code, "c" récupère en fait les attributs de l'objet qui fait l'addition, dans la fonction "calculeAddition", ce qui me va pour les 3 int, mais pas pour le nom. Bon, j'ai cru comprendre que l'exemple utilisé dans le cours est juste confus, & peut-être pas le meilleur moyen d'aborder les classes..

            Que me conseillerais-tu ? Laisser tomber mon problème & redémarrer le cours à 0 en utilisant celui-ci ? :

            http://guillaume.belz.free.fr/doku.php?id=programmez_avec_le_langage_c

            Encore merci pour la réponse

            Ps: je comprends que ma phrase d'intro ait pu faire bizarre.. en gros, ma situation est la suivante :  l'école où je dois rentrer début Octobre pour une formation de 5 ans, me laisse une chance de démarrer en 2ème année (j'ai 38 ans), seulement si j'arrive à avaler le programme info de 1ère année d'ici la rentrée.. j"essaye seulement de me donner les chances d'y arriver (:

            • Partager sur Facebook
            • Partager sur Twitter
              19 juin 2019 à 12:30:28

              Pour les notions que tu n'as pas abordées:
              - explicit est importante. Je te laisse la voir en temps voulu
              - friend, tout le monde la traite à un moment ou un autre, mais la raison pour laquelle je l'utilise ici est une raison "avancée" et absolument pas celle que tu aurais pu voir en cours (enfin... je suis tombé sur un collègue qui a eu un vrai prof dans son cursus (Joel Falcou pour ceux qui connaissent), donc bon...). Je ne te parlerai pas de cette raison. En revanche, si je n'avais pas mis friend, il aurai alors été nécessaire de sortir les deux définitions de ces fonctions de la définition de la classe. Certains déclarent l'opérateur + comme une fonction membre (avec un seul argument explicite donc), c'est maladroit.

              A propos de op+, j'avais commis une faute de frappe. Il retourne non pas "lhs ++ rhs", mais "lhs += rhs" -- j'ai rétrocorrigé. Hormis optimisations pour grosses classes, le code que j'ai donné est l'écriture canonique (malheureusement pas pratiquée partout) de définir les opérateurs mathématiques sur les classes légères.

              Je ne comprends vraiment pas ta problématique de "conservation". += ne va pas laisser laisser l'état de l'opérande gauche inchangé, c'est le contraire: il le modifie. C'est normal. On calque le comportement du C. C'est voulu, c'est le principe de moindre surprise en gardant une sorte de rétrocompatibilité.

              Peut-être... peut-être...
              Ce qui aurait été plus propre pour ton calcule addition (et je me demande si ce n'est pas ça que tu cherches), c'est:
              - option 1: Tu calcules ton nouvel état dans trois variables locales, et à la fin tu retournes le résultat en passant par le constructeur: "return Duree(h, m, s);"
              - option 2: Ton constructeur se charge de s'assurer que les invariants sont bons, c'est à dire qu'il va propager les secondes au delà de 60 aux minutes, et les minutes au delà de 60 aux heures. NB: S'assurer du respect des invariants est le rôle primordial du constructeur. Ton addition se fait alors avec

              /**
               * @invariant min < 60
               * @invariant sec < 60
               * @warning reste la question de savoir qui porte le signe pour les durées négatives...
               */
              struct DureeMaladroite
              {
                  DureeMaladroite(int h, int m, int s)
                  : heure(h), min(m), sec(s)
                  {
                      min += sec / 60;
                      sec = sec % 60; // pas besoin de chercher des feintes pour optimiser le nombre de divisions, les compilos/machines contemporaines calculent modulo et résultat de la division entière ensemble, et de façon intelligente
              
                      heure += min /60;
                      min = min % 60;
                      // et tada!!! l'invariant est respecté
                      assert(min < 60);
                      assert(sec < 60);
                      // Et l'information de départ est bien stockée sans perte
                      assert(heure*3600+min*60+sec == h*3600+m*60+s);
                   }
              
                   friend DureeMaladroite operator+(DureeMaladroite const& lhs, DureeMaladroite const& rhs)
                   { return Duree(
                             lhs.heure + rhs.heure, 
                             lhs.min   + rhs.min, 
                             lhs.sec   + rhs.sec);
                   }
              private:
                   int heure;
                   int min;
                   int sec;
              };

              Note: c'est ce que j'ai fait dans un démonstrateur de classe de nombre rationnel: http://ideone.com/DOCWOy

              Pour l'histoire de la formation... Cela aurait été sympa qu'ils te fournissent leurs supports de cours... Dans les faits, peu de formations forment correctement à la manipulations du C++, et nous sommes très critiques dans les communautés en ligne. Ce qui veut dire que l'on peut te pousser vers les bonnes pratiques que l'on attend de nos collègues (nous qui participons à cette communauté en ligne), et cela peut être à des années lumières de ce qu'encore trop de profs enseignent. Mes soupçons sont que le C++ de 1ere année (sur 5) a dû être survolé -- mais je peux me tromper.

              AMA, ce qui va être important: que tu saches pondre et mettre en oeuvre des algos. Et suivant le cursus à venir, manipuler les éléments des
              langages qui seront employés dans la suite du cursus.

              -
              Edité par lmghs 19 juin 2019 à 12:33:18

              • Partager sur Facebook
              • Partager sur Twitter
              C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                19 juin 2019 à 18:19:45

                Merci de nouveau pour ta réponse

                En effet pour l'opérateur +=, j'ai bien compris le principe, & c'est cet opérateur que le cours de Mathieu propose d'utiliser pour l'opérateur +

                /*
                Duree operator+(Duree const &a, Duree const &b)
                {
                  Duree copie(a);
                  copie += b;
                  return copie;
                }
                */

                Je rappelle mon constructeur

                Duree::Duree(string nom, int heures, int minutes, int secondes) :
                m_nom(nom), m_heures(heures), m_minutes(minutes), m_secondes(secondes)

                & ce que je cherchais à faire, c'est lorsque une addition d'objets (Duree) est effectuée dans le main :

                Duree dureeResultat("DureeResultat");
                dureeResultat = duree1 + duree2;

                que dureeResultat puisse conserver son attribut "m_nom" après l'addition.

                C'est pour cela que j'ai plutôt essayé de faire ça pour l'opérateur +

                Duree operator+(Duree const &a, Duree const &b)
                {
                  Duree resultat;
                  return resultat = resultat.calculeAddition(a, b);
                }

                utilisant cette fonction :

                Duree Duree::calculeAddition(Duree const &a, Duree const &b)
                {
                  Duree resultat(0, 0, 0);
                
                  resultat.m_secondes = a.m_secondes + b.m_secondes;
                  if (resultat.m_secondes >= 60){
                    resultat.m_minutes += resultat.m_secondes / 60;
                    resultat.m_secondes %= 60;}
                  resultat.m_minutes += a.m_minutes + b.m_minutes;
                  if (resultat.m_minutes >= 60){
                      resultat.m_heures += resultat.m_minutes / 60;
                      resultat.m_minutes %= 60;}
                  resultat.m_heures += a.m_heures + b.m_heures;
                
                  return resultat;

                avec l'espoir qu'en déclarant dans cette fonction, un constructeur de l'objet "Duree" n'utilisant pas d'attribut "m_nom", cela n'ecraserait pas

                la valeur "m_nom" de l'objet "dureeResultat" qui est créé en utilisant un constructeur déclarant l'attribut "m_nom"..  mais sans succès, à l'affichage, dureeResultat se retrouve avec un attribut m_nom effacé après utilisation de la fonction. Je te remets le l'appel du main puis l'affichage console

                Duree dureeResultat("DureeResultat"); // je déclare mon objet en utilisant le constructeur avec attribut m_nom
                      dureeResultat.afficher();       
                      dureeResultat = duree1 + duree2;
                      dureeResultat.afficher();
                Duree 1 : 4h51m37s
                
                Duree 2 : 2h11m45s
                
                DureeResultat : 0h0m0s                // objet bien créé avec attribut m_nom & les 3 attributs int à 0
                
                 : 7h3m22s                            // après être traîté par la fonction calculeAddition, attribut m_nom effacé..

                Voilà, l'idée était juste de contenir, dans un objet Duree à l'attribut m_nom invariable, le résultat de l'addition de 2 autres durées.. c'est pour cela que je ne voulais pas utiliser l'opérateur +=, ce dernier conservant effectivement les attributs du 1er objet de l'operande, ajoutés à ceux du 2ème objet.

                Je vais essayer de décortiquer ça, il me semble que dans les 2 options que tu m'as proposé, je devrais trouver ma solution. Merci pour les petits raccourcis algo, en effet, contrairement à ce qui est dit dans le cours, les "if" n'ont pas l'air nécéssaires pour s'assurer que les unités secondes-minutes ne dépassent pas 60 & ton idée d'avoir un objet Durée contenant un seul attribut (valeur en secondes), & seulement faire les conversions minutes/heures pour l'affichage ou l'utilisation dans les fonctions, à l'air bien + pratique (: 

                Ps: "assert", voilà une nouvelle notion à découvrir 

                Pss: pour l'anecdote, voilà mes directives (copier coller du mail reçu par le prof d'info) pour essayer d'être prêt pour rentrer en 2ème année:

                - Algorithmie et langage C :

                => Installer un environnement de développement (GCC, Visual Studio, Eclipse...)       // J'utilise Code:Blocks & Atom

                => Travailler les notions de bases : créer une fonction, l'appeler depuis le "main", gérer des variables, faire des tests (if else), faire des boucles (for, while, do while), implémenter une fonction récursive

                - Langage C++

                => Créer une classe avec attributs et méthodes private et public, étudier la notion de protected

                => Étudier l'héritage et faire un instanciation d'un objet ayant un autre objet comme parent. Comprendre la portée des variables de l'objet parent vers l'objet enfant.

                Il me reste à voir la fonction récursive en C, & pour le C++, j'arrivais juste à la partie du cours de Mathieu qui allait (je pense) aborder les notions "protected", "Heritage" etc.. pour me permettre de valider les notions & voilà que je me noies sur une notion surement facultative (l'objet du post), & que je croise tonne de posts qui disent que ce cours est confus & obsolète TT.. je ne suis plus trop sûr de la direction à prendre, reboot le cours, ou continuer malgré les défauts de ce dernier évoqués par beaucoup..
                Pss: Après ça, me reste aussi HTML/Css mySQL/PHP Merise/UML, à faire avant fin août...mais ça, c'est une autre histoire
                Désolé pour la longueur du post, merci pour le temps passé à me lire

                EDIT: Pour préciser ce que j'ai fais comme apprentissage ces dernières semaines : les 2/3 du cours langage C (j'ai stop à partir de la SDL) de Mathieu sur ce site, 1/3 du cours C++ de Mathieu aussi sur ce site (j'en suis juste avant le TD Zfraction)

                -
                Edité par Nektar_ia 19 juin 2019 à 18:44:45

                • Partager sur Facebook
                • Partager sur Twitter
                  19 juin 2019 à 19:35:01

                  Ca y est j'ai compris. Tu as rajouté un champ qui cherche à transformer la valeur en entité d'une certaine façon.

                  Soyons clair, ça n'a aucun intérêt: le nom d'une variable peut changer d'une portée à l'autre. En plus les valeurs (99% de toutes les données numériques) ont pour vacation à être copiées. Autant dire qu'elles n'ont aucune notion d'identité. En s'en moque éperdument.

                  Techniquement, dans ton cas, il aurait fallu que tu définisses ton propre opérateur d'affectation pour procéder à l'affectation de tous les champs sauf le nom.

                  Dans tous les cas oublie sur du vrai code. Là, tu perds du temps (surtout si tu as une contrainte temporelle) sur un élément qu'on ne fait jamais et que l'on ne voudra jamais voir en production.

                  ---------------

                  Pour assert(), réserve ça à plus tard je pense. Cela s'inscrit dans un cade plus global de "programmation par contrat", cf les 3 derniers billets de mon blog (signature).

                  ---------------

                  Côté environnement, je donne des formations en interne dans ma boite, mes derniers apprenants ont tous utilisé QtCreator dans un environnement Linux récent (une fedora récente) et ... c'est franchement pas mal. Je pense que tu devras avoir quelque chose de pas mal avec Visual Studio aussi si jamais tu es sous Windows. Je ne suis pas persuadé que Code Block soit encore aussi pertinent que ce qu'il fut dans le passé.

                  ---------------

                  Parenthèse.

                  protected c'est ... un mauvais réflexe par défaut. C'est acceptable sur des fonctions membres qui pourront être accédées depuis des classes filles. Et c'est à proscrire par défaut sur des données. L'encapsulation est une histoire d'invariants: une classe à des invariants, et on ne peut pas faire confiance à du code externe pour éviter de modifier n'importe comment nos données internes.

                  Sur l'exemple de la DureeMaladroite, il est hors de question que quiconque puisse mettre d.min à 61. Que cela soit du code externe (ce qui serait garanti avec private et protected), mais aussi du code enfant (*) que seul private pourrait garantir. Si min est protégée, un code enfant pourrait faire n'importe quoi. S'il a vraiment besoin d'y toucher, on peut à la place offrir une fonction protégée dédiée (et restreinte aux seuls enfants), qui garantie le respect des invariants.

                  (*) dériver (publiquement) la DureeMaladroite est une erreur de conception, dont on ne te parlera pas en cours -- cherche "entité et valeurs")

                  -------------------

                  Oublie le cours qui est ici pour la partie héritage. Ils font des trucs compliqués totalement bancals et qu'ils ne faut surtout ne jamais faire.  Je ne sais plus où en est celui de Guillaume sur ce sujet.

                  Je vois qu'il ne disent rien sur la gestion des ressources :/ C'est un sujet vite compliqué quand on montre n'importe quoi aux débutants alors qu'avec les types dédiés cela peut être bien plus simple sur beaucoup de points (string, vector...) -- imagine que l'on enseigne les déclinaisons du latin avant d'enseigner l'usage du français.

                  Bref. Je m'égare.

                  • Partager sur Facebook
                  • Partager sur Twitter
                  C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                    19 juin 2019 à 20:30:05

                    Ok, c'est ce que je redoutais, je perds du temps sur un truc inutile /facepalm.

                    J'espère trouver ce dont j'ai besoin dans le cours de Guillaume, après tout, je cherche juste à avoir les bases du langage + un chapitre bien fait pour les notions de Classes / Heritage (enfant/parent), la notion protected, & si possible un bon gros TD sur le sujet.

                    Je te remercie une dernière fois pour le temps que tu as passé à me répondre, j'ai bookmark ton blog (je l'ai survolé, j'y ai déjà croisé la métaphore sur le latin :D), je le lirai quand j'aurai le niveau.

                    Ps: Je viens de voir que tu bosses dans l'industrie aérospatiale.. c'est ce que je vise aussi, du coup, je te bookmark aussi dans ma tête :D

                    -
                    Edité par Nektar_ia 19 juin 2019 à 20:33:04

                    • Partager sur Facebook
                    • Partager sur Twitter

                    Surcharger un opérateur : rendre un attribut const

                    × 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