Partage
  • Partager sur Facebook
  • Partager sur Twitter

Problème Polymorphisme

    11 novembre 2017 à 22:26:51

    Bonsoir à tous,

    Alors voilà, j'ai un soucis.  J'ai créé une classe Pixel, qui hérite d'une classe Forme.  J'ai redéfinir la méthode setId(), qui est déclarée dans la classe Forme dans ma classe Pixel.  Le problème, c'est que lorsque que je déclare un objet Pixel et que j'utilise la methode setId(), c'est celle de forme qui est appelée, et pas celle de Pixel. J'ai pourtant bien déclaré setId() en virtuelle dans Forme. J'ai donc surement du louper quelque chose.

    Le prototype dans la classe Forme :

    virtual void setId(const char *)

    Celui de la classe Pixel : 

    void Pixel::setId(const char *)



    et le main : 

    int main()
    {
      Forme *pf;
      pf = new Pixel();
      
      pf->setId("Hello");
    
      return 0;
    }

    Si quelqu'un est capable de m'aider, je le remercie d'avance ! :)

    -
    Edité par Steez1 11 novembre 2017 à 22:34:07

    • Partager sur Facebook
    • Partager sur Twitter
      11 novembre 2017 à 22:42:01

      Bonsoir,

      En principe c'est censé fonctionner :

      class A
      {
      public:
          virtual void affiche() { std::cout << "A" << '\n'; }
      };
      
      class B : public A
      {
      public:
          virtual void affiche() { std::cout << "B" << '\n'; }
      };
      
      /////////// MAIN ///////////
      int main()
      {
          A *ptr = new B;
      
          ptr->affiche(); //Affiche B
      
          delete ptr;
      
          return 0;
      }

      Au niveau de tes classes ça donne quoi ?

      • Partager sur Facebook
      • Partager sur Twitter

      ...

        11 novembre 2017 à 22:52:55

        La classe Forme :

        class Forme
        {
        
        	protected :
        		char *id;
        		Point position;
        		Couleur *color;
        		int profondeur;
        		static int cpt;
        		char *infos;
        		
        	public :
        		
        		Forme();
        		Forme(const char*,Point);
        		Forme(const char*,Point,const Couleur*,int);
        		Forme(Forme const&);
        		~Forme();
        		virtual void setId(const char *);
        		void setPosition(Point);
        		void setCouleur(const Couleur *);
        		void setProfondeur(int);
        		int getProfondeur() const;
        		Point getPosition() const;
        		Couleur* getCouleur() const;
        		static int getCompteur();
        		char * getId() const;
        		void Affiche() const;
        		const char * getInfos(void) {return 0;}
        		
        };


        et la classe Pixel :

        using namespace std;
        
        #include <string.h>
        #ifndef PIXEL_H_INCLUDED
        #define PIXEL_H_INCLUDED
        
        #include <iostream>
        #include "Forme.h"
        #include "BaseException.h"
        
        
        class Pixel : public Forme
        {
        	public :
        	
        		// DEFAUT
        		Pixel();
        		
        		// PARTIEL
        		Pixel(const char*,Point);
        		
        		//COMPLET
        		Pixel(const char *,Point,const Couleur *,int);
        		
        		//COPIE
        		Pixel(Pixel const &);
        		
        		~Pixel();
        		
        		
        		
        		
        		friend ostream& operator<<(ostream&,const Pixel &);
        		
        		const char * getInfos(void);
        		void setId(const char *);
        		
        		
        		
        };



        • Partager sur Facebook
        • Partager sur Twitter
          11 novembre 2017 à 22:56:40

          Salut,

          Alors, la première chose, c'est qu'une fonction setId, ce n'est vraiment pas l'idée du siècle: un identifiant, ca ne change par nature absolument pas.  C'est typiquement le genre d'information qui devrait être définie "une bonne fois pour toute" (au travers du constructeur de ton objet)

          De plus, même s'il y avait un intérêt quelconque à modifier l'identifiant de quelque chose (et cela reste encore vraiment à prouver), le comportement qui permettrait de modifier cet identifiant n'aurait absolument aucun intérêt à être polymorphe: à partir du moment où toutes tes classes dérivées disposent (au travers de l'héritage) d'un identifiant qui est de type identique, le comportement qui permet de redéfinir la valeur de cet identifiant sera -- forcément -- identique pour toutes les classes dérivées.

          Si bien que, quoi qu'il en soit (en termes d'intérêt de proposer une fonction setId), il n'y a absolument aucune raison de rendre cette fonction virtuelle.

          On envisagera la création de fonction polymorphe (comprends: de fonction virtuelle) lorsque ces fonctions correspondent à un service pour lequel il est sans doute nécessaire de prévoir le moyen d'adapter le comportement à l'élément réellement utilisé.

          Par exemple, nous pourrions créer toute une hiérarchie de classes regroupant les différentes formes avec laquelle on a l'habitude de travailler : le cercle (ou le disque), le triangle, le rectangle (atttention!!! contrairement à ce que l'on a appris à l'école primaire, une classe Carre ne peut pas hériter de la classe Rectangle ;) ) le polygone, ...

          Nous nous attendrions sans doute à pouvoir tracer toutes ces formes, si bien que notre classe de base (appelons la Shape) devra fournir un service (qui sera la fonction draw() ) qui permette le tracer des différentes formes.

          Mais bien sur, tu vas très certainement t'y prendre différemment pour tracer les différentes formes "concrètes" que tu envisages.  Et du coup, tu auras de fait une excellente raison pour faire en sorte que ta fonction draw soit polymorphe.

          Mieux encore, étant donné que tu n'as, a priori, aucune information au niveau de ta clase Shape qui lui permettra de se tracer  (alors qu'elle ne sait même pas quel type de forme elle représente), tu créeras sans doute une fonction vrituelle pure, qui aura pour résultat que l'on ne pourra créer une instance de des classes dérivées pour lesquelles nous aurons défini la manière dont la fonction draw() doit fonctionner.

          • 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
            11 novembre 2017 à 23:26:26

            Et au niveau de la définition de setId pour Forme et Pixel ça donne quoi ?

            -
            Edité par Guit0Xx 11 novembre 2017 à 23:35:27

            • Partager sur Facebook
            • Partager sur Twitter

            ...

              12 novembre 2017 à 21:41:18

              Merci de votre aide mais il semblerait que le problème se soit réglé de lui-même, je n'ai pas trop compris comment, m'enfin soit ! Bonne soirée à tous ! :)

              • Partager sur Facebook
              • Partager sur Twitter
                12 novembre 2017 à 21:50:04

                Steez1 a écrit:

                Merci de votre aide mais il semblerait que le problème se soit réglé de lui-même, je n'ai pas trop compris comment, m'enfin soit ! Bonne soirée à tous ! :)

                Ou là!!!  Un problème qui se "règle tout seul", "on ne sait pas trop comment"???  Ca, c'est un problème qui ressurgira juste au moment plus inapproprié possible.  Par exemple, juste au moment de la démonstration de ta fantastique application :p

                Quand un problème est résolu, il y a toujours une raison pour qu'il le soit. Tu ferais bien de t'habituer à chercher cette raison et à essayer de la comprendre.  C'est encore le meilleur moyen pour éviter de reproduire le problème par la suite ;)

                • 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
                  12 novembre 2017 à 22:02:54

                  Guit0Xx a écrit:

                  Bonsoir,

                  En principe c'est censé fonctionner :

                  class A
                  {
                  public:
                      virtual void affiche() { std::cout << "A" << '\n'; }
                  };
                  
                  class B : public A
                  {
                  public:
                      virtual void affiche() { std::cout << "B" << '\n'; }
                  };
                  
                  /////////// MAIN ///////////
                  int main()
                  {
                      A *ptr = new B;
                  
                      ptr->affiche(); //Affiche B
                  
                      delete ptr;
                  
                      return 0;
                  }

                  Au niveau de tes classes ça donne quoi ?

                  Ce code est completement bugué, on C++, on utilise l'operateur new que si on a une bonne raison de le faire, et que l'on endosse toutes les consequences (exceptions entre autre), ce que ce code ne fais pas.
                  Ca aurais été tout aussi fonctionnel avec des references.

                  @Steez1:
                  Ton code est bourré de pointeurs, ca sent les allocation dynamiques et fuites de mémoire à plein nez.
                  As-tu réellement besoin ?
                  La classe string ne peut-elle faire le job ?

                  • Partager sur Facebook
                  • Partager sur Twitter
                    12 novembre 2017 à 22:20:09

                    Deedolith a écrit:

                    Ce code est completement bugué, on C++, on utilise l'operateur new que si on a une bonne raison de le faire...

                    T'en fais pas je suis au courant, je ne voulais pas l'inonder tout de suite d'explications sur les "smart pointers". C'était juste pour l'exemple et rester concentré sur son problème de base.

                    -
                    Edité par Guit0Xx 12 novembre 2017 à 22:37:53

                    • Partager sur Facebook
                    • Partager sur Twitter

                    ...

                      13 novembre 2017 à 1:24:41

                      Poste au moins un code correcte ...

                      class A
                      {
                      public:
                      	virtual void affiche() { std::cout << "A" << '\n'; }
                      };
                      
                      class B : public A
                      {
                      public:
                      	virtual void affiche() { std::cout << "B" << '\n'; }
                      };
                      
                      /////////// MAIN ///////////
                      int main()
                      {
                      	B item;
                      	A& ref = item;
                      
                      	ref.affiche(); //Affiche B
                      
                      	return 0;
                      }



                      • Partager sur Facebook
                      • Partager sur Twitter
                        13 novembre 2017 à 10:10:25

                        Salut.

                        J'ai été un peu confus dans ton code. Tout d'abord de manière générale je ne vois du tout pourquoi il y aurait un lien d'héritage entre un pixel et une forme... mais passons.

                        Les méthodes virtuelles ne marchent bien que quand les objets sont construits. Dans le constructeur ce sont les méthodes de la classe construite qui sont appelées.

                        EDIT : destructeurs virtuels et override (voir post suivant)

                        struct Base{
                          Base(){f();}//ici toujours "Base::f"
                          virtual void f(){std::cout << "Base"<<std::endl;}
                        };
                        
                        struct Derived :Base{
                          Derived(){f();}//ici Derived::f
                          virtual void f()override{std::cout << "Derived"<<std::endl;}
                          virtual ~Base(){}
                        };
                        
                        
                        //---
                        void test(){
                          Derived d;
                          //construit la calsse de base :affiche Base
                          //construit la classe derivée : affiche Derived
                        }
                        
                        
                        




                        Si tu veux proffiter de la virtualité, tu dois faire une construction en deux temps, avec par exemple une fonction init

                        struct Base{
                          Base(){}//rien
                          virtual ~Base(){}
                          virtual void f(){std::cout << "Base"<<std::endl;}
                          void init(){f();}//ici résolution vituelle
                        };
                        
                        struct Derived :Base{
                          Derived(){}
                          virtual void f() override {std::cout << "Derived"<<std::endl;}
                          //init est hérité, pas besoin de le recopier
                         };
                        
                        
                        //---
                        void test(){
                          Derived d;
                          //rien
                          d.init(); 
                          //Appelle Base::init
                          //Résolution virtuelle appelle Derived::f
                          
                        }
                        




                        -
                        Edité par ledemonboiteux 20 novembre 2017 à 10:15:30

                        • Partager sur Facebook
                        • Partager sur Twitter
                          18 novembre 2017 à 21:46:16

                          Tout ça, ça mériterait que les destructeurs soient déclarés virtuels.

                          Et que les redéfinitions soient marquées "override".

                          class A
                          {
                          public:
                              virtual void affiche() {
                                  std::cout << "A" << '\n';
                              }
                              virtual ~A() = default;
                          };
                          
                          class B : public A
                          {
                          public:
                              virtual void affiche() override {
                                  std::cout << "B" << '\n';
                              }
                          };



                          • Partager sur Facebook
                          • Partager sur Twitter
                          Anonyme
                            19 novembre 2017 à 12:53:49

                            Je suis pas sur d'avoir bien compris la definition de cppreference, mais un override serait une verification que les prototypes correspondent, non?
                            • Partager sur Facebook
                            • Partager sur Twitter
                              19 novembre 2017 à 14:08:21

                              BorisD a écrit:

                              Je suis pas sur d'avoir bien compris la definition de cppreference, mais un override serait une verification que les prototypes correspondent, non?


                              Si j'ai bien compris, grosso-modo, oui. Dans un premier temps, ça permet au lecteur de savoir que la fonction héritée est virtual (dans le cas où celle-ci n'est pas précisée dans les classes dérivées).

                              Et ensuite cela permet de s'assurer que l'on garde la même signature et que l'on est pas en train de surcharger la fonction.

                              class A
                              {
                              public:
                                  virtual void affiche() const;
                              };
                              
                              class B : public A
                              {
                              public:
                                  void affiche() const override;      // Ok, signature identique
                              
                                  void affiche() override;            // Erreur
                                  int affiche() const override;       // Erreur
                                  void affiche(int x) const override; // Erreur
                              };

                              -
                              Edité par Guit0Xx 19 novembre 2017 à 15:33:42

                              • Partager sur Facebook
                              • Partager sur Twitter

                              ...

                                20 novembre 2017 à 0:30:07

                                Pour ce qui est de overrride, cela permet de s'assurer que l'on fait bien les choses, et que l'on redéfini effectivement un comportement polymorphe au lieu de cacher un comportement existant (pas forcément polymorphe) derrière un comportement qui porterait le même nom.

                                Prenez l'exemple d'une classe exposant une fonction non virtuelle comme

                                class Base{
                                public:
                                    /* c'est une classe destinée à entrer dans une hiérarchie
                                     * de classe.  Elle a donc sémantique d'entité
                                     */
                                    Base(Base const & ) = delete;
                                    Base & operator=(Base const &) = delete;
                                    virtual ~Base() = default;
                                
                                    /* la fonction qui nous intéresse */
                                    void foo(){
                                        std::cout<<"i'm in base\n";
                                    }
                                };

                                Je peux très bien créer une classe qui hérite de base et qui défini un comportement particulier pour foo, sous une forme qui serait proche de

                                class Derived : public Base{
                                public:
                                    void foo(){
                                        std::cout<<"i'm in derived\n";
                                    }
                                };

                                Mais l'utilisation va proposer quelques problème (parce que foo n'est pas virtuelle):

                                int main(){
                                    Derived d;
                                    Base & ref = d;
                                    d.foo(); // OK: affiche "i'm in derived"
                                    ref.foo(); // KO: affiche "i'm in base"
                                }

                                Le mot clé override nous permet de demander au compilateur de vérifier que l'on redéfini bien un comportement polymorphe (une fonction qui est virtuelle à la base).  Ainsi, si je modifie un tout petit peu ma classe dérivée pour y rajouter ce mot clé, sous une forme proche de

                                class Derived : public Base{
                                public:
                                    void foo() override{
                                        std::cout<<"i'm in derived\n";
                                    }
                                };

                                Le compilateur va m'insulter et refuser de compiler au motif (tout à fait légitime) que foo n'est pas une fonction virtuelle dans base

                                Par contre, si je modifie aussi ma classe de base et que je rend la fonction foo virtuelle sous la forme de

                                class Base{
                                public:
                                    /* c'est une classe destinée à entrer dans une hiérarchie
                                     * de classe.  Elle a donc sémantique d'entité
                                     */
                                    Base(Base const & ) = delete;
                                    Base & operator=(Base const &) = delete;
                                    virtual ~Base() = default;
                                
                                    /* la fonction qui nous intéresse */
                                    virtual void foo(){
                                        std::cout<<"i'm in base\n";
                                    }
                                };

                                Le compilateur saura que Base::foo est une fonction virtuelle, et qu'il peut donc me permettre d'en redéfinir le comportement dans Derived.

                                Maintenant, ce cas "simple" ne pose pas vraiment de problème.  Mais imaginez maintenant une fonction pour laquelle vous auriez plusieurs prototypes.  Après tout, le nom d'une fonction représente le service qu'elle rend, et on peut très bien envisager plusieurs possibilités de rendre un service équivalent sur base de différentes données reçues en paramètre.

                                Nous pourrions donc très bien avoir une classe de base qui propose une interface proche de

                                class Base{
                                public:
                                    /* c'est une classe destinée à entrer dans une hiérarchie
                                     * de classe.  Elle a donc sémantique d'entité
                                     */
                                    Base(Base const & ) = delete;
                                    Base & operator=(Base const &) = delete;
                                    virtual ~Base() = default;
                                
                                    /* un seul prototype de la fonction est destiné à être
                                     * polymorphe
                                     */
                                    virtual void foo(){
                                        /* ... */
                                    }
                                    /* le même service (non polymorphe), mais avec des 
                                     * informations en plus
                                     */
                                    void foo(int i){/* ... */}
                                    /* et encore un, pour faire bonne mesure */
                                    void foo(int i, std::string const & j){/* ... */}
                                };

                                Le seul prototype de foo pour lequel nous puissions redéfinir le comportement dans la classe dérivée, c'est celui qui ne prend aucun paramètre.

                                Mais un développeur n'est jamais qu'un utilisateur de ce qui est déjà mis à sa disposition.  Et, en tant que tel, il ne vaut pas mieux que n'importe quel utilisateur, à savoir: c'est un imbécile distrait.

                                On risque donc d'avoir un développeur "un peu distrait" qui aura "l'excellente idée" (à son point de vue, du moins) de vouloir redéfinir le comportement de la version de foo qui prend un int comme paramètre.

                                Sauf que cette version là de foo n'est pas virtuelle, et que l'on n'a donc aucune raison d'en redéfinir le comportement! Et le résultat correspondra à ce que je viens de montrer.

                                Une bonne habitude a prendre est donc d'indiquer systématiquement le mot clé override lorsque vous (croyez) redéfinir un comportement polymorphe, car cela permettra au compilateur de s'assurer que c'est bien ce que vous êtes occupés à faire.  Et de vous engueuler dans le cas contraire ;)

                                • 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
                                  20 novembre 2017 à 11:29:27

                                  koala01 a écrit:

                                  Une bonne habitude a prendre est donc d'indiquer systématiquement le mot clé override lorsque vous (croyez) redéfinir un comportement polymorphe, car cela permettra au compilateur de s'assurer que c'est bien ce que vous êtes occupés à faire.  Et de vous engueuler dans le cas contraire ;)

                                  Les vrais amis, c'est ceux qui vous disent quand vous vous êtes en train de faire une connerie.  Le compilateur est votre ami, le seul qui est là  quand vous êtes en train de programmer.

                                   Parce que ça vient vite les conneries. Par exemple

                                  class A {
                                     virtual void foo(int n) const;
                                     ...
                                  };
                                  
                                  class B : public A {
                                     void foo(int n) override;  // bummer.
                                  };
                                  





                                  -
                                  Edité par michelbillaud 20 novembre 2017 à 11:32:55

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    20 novembre 2017 à 12:03:53

                                    Super ! Merci pour tous ces conseils ;)
                                    • Partager sur Facebook
                                    • Partager sur Twitter

                                    ...

                                    Problème Polymorphisme

                                    × 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