Partage
  • Partager sur Facebook
  • Partager sur Twitter

Polymorphisme et instanciation automatique.

Cherche solution template

Sujet résolu
    16 décembre 2014 à 11:09:44

    Salut !


    Soit le code suivant : 

    std::sharedptr<Forme> Instanciate(int type)
    {
      switch(type)
      {
      case 0:
         return new Cercle;
      case 1:
         return new Line;
      case 2:
         return new Ellipse;
    
      default:
         break;
      }
      return NULL;
    }
    


    En réalité, j'ai un polymorphisme avec beaucoup plus de cas, et donc un switch qui grossit, grossit !

    N'y aurait il pas une possibilité avec du template de faire un truc beaucoup plus compact ?

    J'aimerais écrire quelque part un truc genre {0,Cercle},{1,Line},{2,Ellipse} etc... et qu'ensuite la fonction prenne 4 - 5 lignes.

    Est ce que c'est possible ?

    • Partager sur Facebook
    • Partager sur Twitter

    Recueil de code C et C++  http://fvirtman.free.fr/recueil/index.html

      16 décembre 2014 à 11:22:47

      C'est une factory qu'il te faut.

      Moi généralement j'utilise un truc assez proche de ce que faisait Alexandrescu dans son Modern C++ Design (donc ça doit être dans Loki en plus propre je suppose)

      https://github.com/julienlopez/QSolarSystem/blob/master/common/utils/factory.hpp

      Avec ce fonctionnement, c'est chaque classe qui s'enregistre elle-même auprès de la factory, comme ça pas de soucis d'OCP comme dans ton cas. Ca peut même se factoriser par CRTP comme je le fais dans le projet en lien.

      -
      Edité par epso 16 décembre 2014 à 11:25:41

      • Partager sur Facebook
      • Partager sur Twitter
        16 décembre 2014 à 11:24:09

        Lu'!

        Un truc dans ce genre (avec un identification plus performante)  ?

        //mother.hh
        #ifndef _MOTHER
        #define _MOTHER
        
        #include <functional>
        #include <string>
        #include <map>
        
        class Mother;
        
        namespace std {
          template <typename T>
          using add_pointer_t = typename add_pointer<T>::type;
        }
        using MotherCreator = std::add_pointer_t<Mother*()>;
        
        
        class Mother{
        public :
          virtual ~Mother(){}  
          virtual void print() const;
          
          static Mother* create(std::string const& name);
          static void registerToFactory(std::string const& value, MotherCreator func);
        
        protected:
          //constructeur protégé = seul les amis/enfants peuvent me construire
          Mother(){}
        
        private  :
          //permet d'éviter un "fiasco sur l'initialisation" voir FaQ developpez.com
          static std::map<std::string, MotherCreator>& factories();
        };
        
        #endif
        
        
        //mother.cpp
        #include <iostream>
        #include "mother.hh"
        
        //permet d'éviter un "fiasco sur l'initialisation" voir FaQ developpez.com
        std::map<std::string, MotherCreator>& Mother::factories(){
          static std::map<std::string, MotherCreator> real_factories;
          return real_factories;
        }
        
        void Mother::print() const{ 
          std::cout<<"Je suis Mother"<<std::endl;
        }
        
        Mother* Mother::create(std::string const& name){
          //on cherche la factory nommé "name"
          auto it(factories().find(name));
          
          //on renvoie la construction voulue
          if(it != factories().end()) return it->second();
          else throw "not in factories";
        }
        
        //permet à une factory de s'enregistrer auprès de Mother
        void Mother::registerToFactory(std::string const& value, MotherCreator func){
          factories()[value] = func;
        }
        
        /*
          NOTE :
          
          Il est important de noter que Mother NE CONNAIT AUCUN ENFANT.
          Mieux encore : ELLE N'A PAS BESOIN DE LES CONNAITRE
        
          Résultat : on peut ajouter des enfants sans que cela nécessite d'inclusion
        */
        
        //child.hh
        #ifndef _CHILD
        #define _CHILD
        
        #include "mother.hh"
        
        class Child : public Mother{
        public :
          void print() const override;
        
        private:
          Child(){} //on ne peut être construit que par nos amis
        
          class Factory;               //on cache la Factory
          friend class Child::Factory; //en l'occurrence, notre factory
        
          static Child::Factory* factory; //qui est statique et privée
        };
        
        #endif
        
        //child.cpp
        #include <iostream>
        #include "child.hh"
        
        //Notre factory cachée
        class Child::Factory{
        public :
          Factory(){
            //à la construction qui n'est appelée que dans ce fichier
            //on s'enregistre dans la factory avec un identifiant
            Mother::registerToFactory("Child", create);
          }
        
          static Mother* create();
        };
        
        //on crée la factory
        Child::Factory* Child::factory = new Child::Factory();
        
        //qui renvoie des enfants
        Mother* Child::Factory::create(){
          return new Child();
        }
        
        void Child::print() const{
          std::cout<<"Je suis Child"<<std::endl;
        }
        
        //otherchild.hh
        #ifndef _OTHERCHILD
        #define _OTHERCHILD
        
        #include "mother.hh"
        
        class OtherChild : public Mother{
        public :
          void print() const override;
        
        private:
          OtherChild(){} //on ne peut être construit que par nos amis
        
          class Factory;               //on cache la Factory
          friend class OtherChild::Factory; //en l'occurrence, notre factory
        
          static OtherChild::Factory* factory;
        };
        
        #endif
        
        //otherchild.cpp
        #include <iostream>
        #include "other_child.hh"
        
        //Notre factory cachée
        class OtherChild::Factory{
        public :
          Factory(){
            //à la construction qui n'est appelée que dans ce fichier
            //on s'enregistre dans la factory avec un identifiant
            Mother::registerToFactory("OtherChild", create);
          }
        
          static Mother* create();
        };
        
        //on crée la factory
        OtherChild::Factory* OtherChild::factory = new OtherChild::Factory();
        
        //qui renvoie des enfants
        Mother* OtherChild::Factory::create(){
          return new OtherChild();
        }
        
        void OtherChild::print() const{
          std::cout<<"Je suis OtherChild"<<std::endl;
        }
        
        //main.cpp :
        #include <memory>
        #include "mother.hh"
        
        int main(){
          //NOTE ULTRA-IMPORTANTE :
          // Même ici on a pas besoin de connaître les enfants et leur type !
          // On les crée par leur identifiant !
          // Polymorphisme POWA : On peut ajouter des enfants après avoir codé tout ça
          // les utiliser dans un algo aléatoire : on ne connaît pas les types réels 
          // donc ça marche !
        
          std::unique_ptr<Mother> ptr_c( Mother::create("Child") );
          std::unique_ptr<Mother> ptr_oc( Mother::create("OtherChild") );
          ptr_c->print();
          ptr_oc->print();
          
          return 0;
        }



        • Partager sur Facebook
        • Partager sur Twitter

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

          16 décembre 2014 à 11:49:26

          Sous une forme plus légère (avec des pointeurs maléfiques, juste pour voir le principe)

          Un bête tableau de fonctions qui, chacune, créent une instance

          #include <iostream>
          #include <functional>
          
          using namespace std;
          
          class Forme {
          	public:
          	    virtual ~Forme() {};
          		virtual void hello() = 0;
          };
          
          class Cercle : public Forme {
          public:
          	static Forme* nouveau() {
          		return new Cercle;
          	}
          	void hello() {
          		cout << "je suis un cercle" << endl;
          	}
          };
          
          class Carre : public Forme {
          public:
          	static Forme* nouveau() {
          		return new Carre();
          	}
          	void hello() {
          		cout << "je suis un carré" << endl;
          	}
          };
          
          	
          int main(int argc, char **argv)
          {
          	function<Forme *(void)> fabriques[] = 
          		{ 
          			Cercle::nouveau, 
          			Carre::nouveau
          		};
          	
          	auto a = fabriques[1]();
          	a->hello();
          	return 0;
          }
          



          • Partager sur Facebook
          • Partager sur Twitter
            16 décembre 2014 à 11:53:18

            Justement, ce que je reproche à cette solution est au final la même que le switch, problème d'OCP.

            Quand on rajoute une classe, il faut pas oublier d'aller aussi modifier le tableau, et c'est vite arrivé^^

            Alors qu'avec une factory (bien entendu, pas forcément besoin de template, on peut faire simple uniquement pour cette classe, les templates sont juste là pour ne pas avoir à réécrire la même chose pour chaque hierarchie de classe), c'est que les classe s'enregistrent elles-même au niveau de la factory, comme ça y a pas de chance d'oublier (où alors si ce qui faut pour n'est pas fait, ça compile pas).

            Bon après tout n'est pas blanc non plus, le soucis des factory comme ça si elles fournissent elles-même leur clé, c'est qu'il faut s'assurer que cette clé est unique...

            -
            Edité par epso 16 décembre 2014 à 11:54:54

            • Partager sur Facebook
            • Partager sur Twitter
              16 décembre 2014 à 12:03:28

              Très intéressant concept !

              Merci à vous deux !

              Avez vous un livre à me conseiller sur les design pattern ? Merci !

              • Partager sur Facebook
              • Partager sur Twitter

              Recueil de code C et C++  http://fvirtman.free.fr/recueil/index.html

                16 décembre 2014 à 12:18:06

                epso a écrit:

                Justement, ce que je reproche à cette solution est au final la même que le switch, problème d'OCP.

                Quand on rajoute une classe, il faut pas oublier d'aller aussi modifier le tableau, et c'est vite arrivé^^

                Alors qu'avec une factory (bien entendu, pas forcément besoin de template, on peut faire simple uniquement pour cette classe, les templates sont juste là pour ne pas avoir à réécrire la même chose pour chaque hierarchie de classe), c'est que les classe s'enregistrent elles-même au niveau de la factory, comme ça y a pas de chance d'oublier (où alors si ce qui faut pour n'est pas fait, ça compile pas).

                Bon après tout n'est pas blanc non plus, le soucis des factory comme ça si elles fournissent elles-même leur clé, c'est qu'il faut s'assurer que cette clé est unique...

                -
                Edité par epso il y a 16 minutes


                Je voulais juste montrer la base du mécanisme. Tu remplace le tableau figé par une map identifiant->fonction, c'est le même truc.

                Après je dois avoir quelque part un truc encore plus mieux, qui est le chargement dynamique (dlopen pour linux) des classes (plugins) présentes dans un répertoire, qui s'enregistrent toutes seules.

                -
                Edité par michelbillaud 16 décembre 2014 à 12:19:13

                • Partager sur Facebook
                • Partager sur Twitter
                  16 décembre 2014 à 12:50:26

                  Le tutoriel de Dvp date un peu, mais il reste intéressant a lire : http://come-david.developpez.com/tutoriels/dps/. Modern C++ Design et le GoF sont intéressant a lire aussi (forcement). On trouve aussi pas mal de tutos sur internet (sur SO en particulier) pour des implémentations plus "moderne" des DP (par exemple utiliser un variadic template pour create, pour construire des objets en utilisant une liste d'arguments différentes selon le type d'objet).

                  Il est également possible de faire des versions compile-time de factory, mais j'aime bien la version runtime + plugin (voir par exemple le code de Qt Creator - et plus généralement de Qt, il utilises pas mal cette approche).

                  @Ksass`Peuk: il est quand même préférable de séparer la factory de la classe Mother. Et d’éviter la static map

                  -
                  Edité par gbdivers 16 décembre 2014 à 12:55:02

                  • Partager sur Facebook
                  • Partager sur Twitter

                  Polymorphisme et instanciation automatique.

                  × 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