Partage
  • Partager sur Facebook
  • Partager sur Twitter

Pointeur d'une classe vers une autre

Masquage et mise à jour de l'objet pointé

Sujet résolu
    19 février 2024 à 19:22:27

    Bonjour,

    J'essaie de faire un petit programme en c++ dans lequel je crée une graine de la classe Mousse, qui hérite de la classe Arbre. Je souhaite ensuite la planter dans une plantation de la classe Plantation.

    Voici la classe Arbre:

    #ifndef ARBRE_HPP_INCLUDED
    #define ARBRE_HPP_INCLUDED
    
    #include <iostream>
    #include <string>
    #include <cmath>
    #include <vector>
    #include <fstream>
    
    class Arbre
    {
        //Les méthodes
    
    public:
    
        Arbre();
        ~Arbre();
        virtual void afficherInfo() const;
        void recevoirEau();
        bool grandir();
        bool adulte() const;
        int getValeur() const;
    
    protected:
    
        //Les attribus
        int m_XPStage[5];
        int m_XPActuel;
        int m_niveau;   // 0 = graine, 1 = germe, 2 = Arbrissaut, 3 = petit, 4 = moyen, 5 = adulte
        int m_valeur;
        std::string m_listEtats[6];
        std::string m_etat;
    
    };
    
    
    #endif // ARBRE_HPP_INCLUDED

     classe Mousse:

    #ifndef MOUSSE_HPP_INCLUDED
    #define MOUSSE_HPP_INCLUDED
    
    #include <iostream>
    #include <string>
    #include <cmath>
    #include <vector>
    #include <fstream>
    
    class Mousse : public Arbre
    {
    public:
        Mousse();
        ~Mousse();
        virtual void afficherInfo() const;
    
    protected:
        std::string m_nom;
        std::string m_description;
    
    };
    
    
    #endif // MOUSSE_HPP_INCLUDED
    

    et la classe Plantation:

    #ifndef PLANTATION_HPP_INCLUDED
    #define PLANTATION_HPP_INCLUDED
    
    
    class Plantation
    {
    public:
        Plantation();
        ~Plantation();
        void recevoirGraine(Arbre agraine);
        void arroser();
        int recolter(int argent);
        virtual void afficherInfoP();
        bool occupe();
    
    protected:
        int m_taille;
        Arbre* m_graine;
    };
    
    #endif // PLANTATION_HPP_INCLUDED

    Dans mon main j'ai ceci:

        Mousse graine; //création de la graine
        Plantation plantation; // création de la plantation
    
        plantation.recevoirGraine(graine); // assigner la graine à la plantation
        plantation.arroser(); // appel à la fonction arroser de la plantation, qui fait appel à la fonction recevoirEau de la graine
        graine.afficherInfo(); // afficher les infos de la graine avec la fonction afficherInfo de graine
        plantation.afficherInfoP(); afficher les infos de la graine avec la fonction afficherInfoP de plantation, qui fait appel à la fonction afficherInfo de Arbre
        graine.afficherInfo(); // afficher les infos de graine avec la fonction afficherInfo de graine
    

    Les fonctions recevoirGraine, arroser et afficherInfoP de la classe Plantation sont celles-ci:

    void Plantation::recevoirGraine(Arbre agraine)
    {
        if(occupe())
        {
            cout<<"Cette parcelle est déjà occupée."<<endl;
        }
        else
        {
            m_graine = &agraine;
        }
    
    }
    
    
    void Plantation::afficherInfoP()
    {
        if(occupe())
        {
            m_graine->afficherInfo(); //Cela fait appel à la Arbre::afficherInfo() plutôt que de faire appel à Niche::afficherInfo()
        }
        else
        {
            cout<<"Parcelle vide"<<endl;
            cout<<"Taille: "<<endl;
        }
    
    }
    
    void Plantation::arroser()
    {
        if(occupe())
        {
            m_graine->recevoirEau();
        }
        else
        {
            cout<<"Il n'y a rien à arroser sur cette parcelle."<<endl;
        }
    
    }

    et la fonctions afficherInfo de la classe Mousse est:

    void Mousse::afficherInfo() const
    {
        cout<<"Nom: "<<m_nom<<endl;
        cout<<"Description: "<<m_description<<endl;
        Arbre::afficherInfo();
    }

     et les fonctions afficherInfo et recevoirEau de la classe Arbre sont:

    void Arbre::afficherInfo() const
        {
            cout<<"Etat: "<<m_etat<<endl;
            cout<<endl;
            cout<<"Evolution: ";
            cout<<m_XPActuel<<" / "<<m_XPStage[m_niveau]<<endl;
            cout<<endl;
            cout<<"Valeur: "<<m_valeur<<endl;
    
        }
    
    void Arbre::recevoirEau()
        {
            m_XPActuel += 1;
            grandir();
        }


    Il y a deux problèmes que je ne comprend pas. Lorsque je fais:

    plantation.afficherInfoP();

    La fonction afficherInfoP fait appel à la fonction afficherInfo de la classe Arbre et non de la classe Mousse, alors que mon objet est de la classe Mousse et que les fonctions sont en virtual.

    Deuxième problème, lorsque que je fais:

        plantation.arroser();
        graine.afficherInfo();
        plantation.afficherInfoP();


    L'objet graine n'est pas mis à jour, et tout se passe comme si la graine n'avait pas été arrosé. Mais l'objet plantation, (qui utilise le afficherInfo de la classe Arbre plutôt que celui de la classe Mousse) renvoit bien des informations qui ont été modifiées par la fonction arroser.

    Savez-vous d'où cela peut venir?

    Merci beaucoup!







    -
    Edité par HélèneRousseau1 19 février 2024 à 19:24:23

    • Partager sur Facebook
    • Partager sur Twitter
      19 février 2024 à 19:33:40

      Le problème vient de cette ligne :

      void recevoirGraine(Arbre agraine);

      Pour expliquer, imagine l'héritage comme des boites. Ta classe Mousse qui dérive de ta classe Arbre, c'est comme si tu avais une "boite" Mousse qui contient une "boite" Arbre.

      Le problème avec cette ligne de code, c'est que tu passe par valeur une "boite" Arbre. Donc c'est comme si tu prenais ta "boite" Mousse de graine et que tu copies uniquement la "boite" Arbre qu'elle contient. Tu perds toutes les informations relatives a ta "boite" Mousse.

      Passer par un pointeur (ou une référence -- mais tu as commencé avec des pointeurs, continue avec ça) permet de passer un lien vers ta "boite" Mousse et donc ne pas perdre les informations d'une classe dérivée.

      void Plantation::recevoirGraine(Arbre* agraine)
      {
          if(occupe())
          {
              cout<<"Cette parcelle est déjà occupée."<<endl;
          }
          else
          {
              m_graine = agraine;
          }
      }
      plantation.recevoirGraine(&graine); // assigner la graine à la plantation

      -
      Edité par gbdivers 19 février 2024 à 19:34:24

      • Partager sur Facebook
      • Partager sur Twitter
        19 février 2024 à 19:39:13

        Merci infiniment, je n'avais pas du tout compris qu'en mettant l'objet arbre en argument cela éliminait déjà les éléments de la classe Mousse.

        Cela marche très bien maintenant!

        • Partager sur Facebook
        • Partager sur Twitter
          19 février 2024 à 19:48:26

          HélèneRousseau1 a écrit:

          en argument 

          Dans tous les cas, c'est "en argument". Mais il existe plusieurs façons de passer un argument, par exemple :

          void foo(int i); // passage par valeur
          void foo(const int& i); // passage par référence constante
          void foo(int& i); // passage par référence
          void foo(int* i); // passage par pointeur
          etc.

          Le passage par valeur (la première ligne -- et ce que tu avais écrit) fait une copie de Arbre et c'est ça qui pose problème. Si tu avais passé une copie de Mousse par exemple, ça aurait marché aussi (mais cela pose d'autres problèmes de faire des copies -- mais laissons de coté pour le moment).

          Les autres lignes permettent de faire une indirection sur l'objet et donc de conserver toutes les informations. C'est ce qu'il y a de mieux à faire.

          Tout ça pour dire que c'est pas juste "passer en argument" qui pose problème, mais la façon de passer l'argument.

          • Partager sur Facebook
          • Partager sur Twitter
            19 février 2024 à 19:59:55

            Salut,

            Déjà, la classe mousse qui hérite de arbre, ca me semble vachement suspect :p

            Parce que cela voudrait dire que toute mousse est un arbre  avec -- peut-être -- "quelque chsoe de plus".

            Et ca, ca a vraiment du mal à passer...

            Qu'à la limite, tu fasses hériter des classes comme Chene, Sapin, Hetre, Noyer ou Pommier, cela pourrait passer, car ce sont effectivement tous des arbres, mais dire qu'une mousse peut être un arbre, définittvement, non... Tu te ferais passer pour un imbécile face à n'importe quel étudiant en agornomie ;)

            Ceci étant dit:

            HélèneRousseau1 a écrit:

            Deuxième problème, lorsque que je fais:

                plantation.arroser();
                graine.afficherInfo();
                plantation.afficherInfoP();


            L'objet graine n'est pas mis à jour, et tout se passe comme si la graine n'avait pas été arrosé. Mais l'objet plantation, (qui utilise le afficherInfo de la classe Arbre plutôt que celui de la classe Mousse) renvoit bien des informations qui ont été modifiées par la fonction arroser.

            Encore une fois, c'est normal...  Parce que, encore une fois, tu as transmis la graine ... par valeur, ce qui a engendré une copie de la graine (enfin, de la composante Arbre de la graine). La graine qui se trouve dans la plantation n'a donc absolument rien à voir avec la graine que tu as transmis à la plantation, ce qui explique que, si tu demande à la graine qui est hors de la plantation d'afficher ses informations, ben, elle ne profite absolument pas des soins apportés par la plantation, car ... la plantation s'occupe d'une autre graine ;)

            Et pour finir, il y a énormément d'erreur dans ton code, cependant, comme il est incomplet et qu'il ne nous permet absolument pas de nous faire une idée de la manière dont tu l'utilise effectivement, il nous sera très difficile de te conseiller quant aux corrections à apporter.

            Peut-être devrais tu mettre ton code complet sur un serveur public git (github,ou autre), que l'on puisse avoir l'ensemble du code et s'arranger pour le faire compiler ;)

            • 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
              19 février 2024 à 20:29:42

              Un truc important pour gagner ENORMEMENT de temps en développant, c'est de faire en sorte de configurer le projet pour que le compilateur détecte, en plus des erreurs, des raisons d'avoir des soucis, et nous mette des avertissements. Le compilateur est votre ami, il a le courage de vous dire franchement que vous faites un truc qui n'a pas l'air bien, même si c'est "légal".

              Sur le code qui suit, version simplifié de celui de dessus, avec l'option -Wall de gcc  on a l'avertissement

              $ g++ -Wall code.cc
              code.cc: In member function ‘void Plantation::recoitGraine(Arbre)’:
              code.cc:16:14: warning: storing the address of local variable ‘a’ in ‘*this.Plantation::m_graine’ [-Wdangling-pointer=]
                 16 |     m_graine = &a;
                    |     ~~~~~~~~~^~~~
              code.cc:15:39: note: ‘a’ declared here
                 15 | void Plantation::recoitGraine(Arbre a) {
                    |                               ~~~~~~^
              

              traduction et explication :

              • le paramètre de recoitGraine est passé par valeur
              • quand on rentre dans la fonction,  une copie temporaire de cette valeur est faite, mise dans la variable locale a, qui sera détruite - la variable locale et la copie avec - en sortant de la fonction.
              • l'affectation  m_graine = &a;  met l'adresse de la copie dans la variable membre (= le champ, l'attribut)  m_graine.
              • quand on ressort de la fonction la copie étant détruite, le pointeur désigne un emplacement qui sera utilisé très vite pour autre chose : il pointe "dans la nature" (dangling pointer)
              • et du coup, c'est à peu près certain que ça va faire planter le programme, quelque part. Souvent sur une opération qui apparemment n'a rien à voir. Le genre de truc mystérieux (*) où on s'arrache les cheveux pendant des heures.

              (*) Pas si mystérieux : si on fait des trucs avec le pointeur, ça bousille le contenu d'une donnée qui occupe l'emplacement mémoire où était la copie temporaire. Et inversement. Mais bon, savoir sur qui ça tombe, c'est une autre paire de manches.

              Moralité :

              • toujours mettre les options de compilation qui provoquent un maximum d'avertissements.
              • avec g++, j'utilise  -Wall et -Wextra. Ca dépend du compilateur utilisé. Voir configuration du projet.
              • dans un programme de débutant, tous ces avertissements doivent être pris en compte et éliminés. En corrigeant le code, pas en supprimant les options.
              • dans un environnement professionnel ça peut arriver d'avoir du code tordu avec des cas qui déclenchent des avertissements alors qu'on fait des trucs honnêtes malgré tout. Du moins on estime que. Et/ou on préfère ne pas se mettre à corriger le code qui est vieux et mal écrit, avec autant de chances de le casser que de le réparer. Don't fix it if it ain't broken. Et des heures de boulot consommées (time is money) pour la bonne cause,  au lieu de faire des trucs qu'on pourrait facturer au client.

              EDIT: zut j'avais oublié de mettre le code, que j'ai rebidouillé après pour essayer un truc. Le voili le voila

              #include <iostream>
              
              class Arbre {
              };
              
              class Plantation
              {
              private:
                  Arbre *m_graine;
              public:
                  void recoitGraine(Arbre a);           // NO GOOD
              };
              
              void Plantation::recoitGraine(Arbre a)  { // NO GOOD
                  m_graine = & a;
              }
              
              int main() {
                  Arbre a;
                  Plantation p;
                  p.recoitGraine(a);
              }

              A faire : passer le paramètre par référence   (Arbre &a)


              PS, après, si la plantation n'avait qu'un type d'arbre défini une fois pour toute, la variable membre m_graine pourrait être une référence initialisée dans le constructeur.


              https://stackoverflow.com/questions/12387239/is-it-idiomatic-to-store-references-members-in-a-class-and-are-there-pitfalls

              ce qui évitera d'avoir à se trimballer un pointeur, mais ne résoudra pas le fond du problème qui fache, quid d'une plantation construite à partir d'un arbre (peut être temporaire) qu'on a détruit ensuite ? Crac boum badaboum.
              La gestion des durées de vie des objets, faut pas compter sur C++. Voir des trucs plus safe, comme Rust.

              -
              Edité par michelbillaud 19 février 2024 à 23:51:23

              • Partager sur Facebook
              • Partager sur Twitter

              Pointeur d'une classe vers une autre

              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
              • Editeur
              • Markdown