IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

object factory et classes templates


Sujet :

C++

  1. #1
    Membre confirmé
    Profil pro
    Inscrit en
    Août 2006
    Messages
    620
    Détails du profil
    Informations personnelles :
    Âge : 47
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Août 2006
    Messages : 620
    Points : 453
    Points
    453
    Par défaut object factory et classes templates
    Bonjour,

    Je bute sur une question dont je ne sais comment trouver la solution... Je veux créer des objets en fonction d'un mot clef dans un fichier de mise en données, et je me dis qu'a priori la bonne façon de faire serait d'utiliser une factory, mais ces objets sont des instances de classes templates et du coup je ne sais pas comment les déclarer auprès de la factory... Pour les classes ordinaire, j'utilise une macro, qui ressemble à ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    #define DECLARE_CRYSTALLO(CLASS_NAME,KEYWORD)                    \
      namespace {                                                           \
        Crystallo* creator(){ return new (CLASS_NAME);}      \
        const string kwords(KEYWORD);                                       \
        const bool registered = Factory<Crystallo>::Instance().Register(kwords,creator); \
    }
    et qui est invoquée dans les fichiers d'implémentation des classes concernées (.C, pour nous).
    Mais pour des classes templates, je n'ai plus de fichier d'implémentation, du coup je ne sais pas trop où je peux invoquer cette macro en ce qui les concerne...
    Pour le moment, le travail est fait ainsi:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
             if((*gr)->GetLineType() == "line")
                _graphs[(*gr)->GetTag()]=new CommandGraph<Line>(*this,
                    (*gr)->GetTag(),
                    graphbox,
                    graphmin,
                    graphmax,
                    egrid);
              else if((*gr)->GetLineType() == "faultedline")
                _graphs[(*gr)->GetTag()]=new CommandGraph<FaultedLine>(*this,
                    (*gr)->GetTag(),
                    graphbox,
                    graphmin,
                    graphmax,
                    egrid);
    Ce que je préfèrerais nettement remplacer par un appel à une O.F. ... Est-ce que je devrais créer un fichier bidon à compiler qui ne contiendrait que des appels à la macro qui va bien pour déclarer ces classes auprès de la factory ? Ou y a-t-ilune arnaque que je ne comprends pas ?

    Merci tout plein !!! :-)

    Marc

  2. #2
    Membre régulier
    Profil pro
    Inscrit en
    Avril 2008
    Messages
    87
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Avril 2008
    Messages : 87
    Points : 111
    Points
    111
    Par défaut
    il me semble en effet que pour faire marcher une factory il faut au moins créer une map qui va lier chaine qui indique ton type, a, e.g. un objet "instanciateur".
    donc a un endroit tu devras bien hard-coder l'instanciation des instanciateurs.

    sinon tu peux toujours mapper des chaines vers des int, et utiliser les int comme indices d'un mpl::vector pour instancier les instanciateurs en lazy.

  3. #3
    Membre expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Points : 3 159
    Points
    3 159
    Par défaut
    Salut !

    Regarde bien ce que tu veux faire. Pour réussir ton tour, tu dois faire la correspondance entre une valeur dynamique (valuée à l'exécution du programme, à savoir le résultat de GetLineType()), et le choix d'un type, sachant que les types sont valués à la compilation. Il faut donc nécessairement faire le lien à un moment ou un autre, entre la valeur dynamique et la classe ou la fonction à laquelle cette valeur correspond en statique.

    Pour moi tu te trompes d'endroit dans la résolution de ton problème. En fait, c'est au moment de valuer l'attribut que renvoie GetLineType que tu devrais faire ce travail.

    Voici un exemple possible avec des pointeurs de fonctions. On peut faire plus élégant encore avec boost::function mais je ne l'ai pas utilisé ici au cas ou tu ne veux pas de boost.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    #include <iostream>
    #include <vector>
    #include <string>
    #include <boost/foreach.hpp>
    #define foreach BOOST_FOREACH
     
    struct Line;
     
    // Celà signifie que "LineDescriptor" représente le type fonction qui prend 0 arguments et renvoie un Line *
    typedef Line* (*LineDescriptor)(); 
     
    template <typename A > struct LineCreator
    {
      static Line* CreateLine()
      {
        return new A();
      }
    };
     
    // Code des classes de ligne
     
    struct Line
    {
      virtual std::string GetName() = 0;
    };
     
    struct SimpleLine : public Line
    {
      virtual std::string GetName()
      {
        return "SimpleLine";
      }
    };
     
    struct FaultedLine : public Line
    {
      virtual std::string GetName()
      {
        return "FaultedLine";
      }
    };
     
     
    int main(int argc, char* argv[])
    {
      // Partie du code ou tu values le line type
      std::vector< LineDescriptor > MesLignes;
      MesLignes.push_back( & LineCreator<SimpleLine>::CreateLine );
      MesLignes.push_back( & LineCreator<FaultedLine>::CreateLine );
     
      // Plus tard, lorsque tu parcours ton tableau
      foreach(LineDescriptor desc, MesLignes)
      {
        Line * pLigne = (*desc)();
        if(pLigne)
        {
          std::cout << pLigne -> GetName() << std::endl;
          delete pLigne;
        }
      }
     
      return 0;
    }
    Ainsi, tu peux éviter des sales rateaux de switch. C'est bien plus efficace que de se taper des comparaisons de chaînes de caractères.

  4. #4
    Membre régulier
    Profil pro
    Inscrit en
    Avril 2008
    Messages
    87
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Avril 2008
    Messages : 87
    Points : 111
    Points
    111
    Par défaut
    http://stackoverflow.com/questions/3...object-factory
    la réponse numéro 2 donne l'exemple le plus commun selon moi.

  5. #5
    Membre expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Points : 3 159
    Points
    3 159
    Par défaut
    Citation Envoyé par Lightness1024 Voir le message
    http://stackoverflow.com/questions/3...object-factory
    la réponse numéro 2 donne l'exemple le plus commun selon moi.
    Le problème de cette approche est qu'il faut remplir la map à l'initialisation, qu'il faut rajouter des lignes dans cette initialisation lors de l'ajout d'une nouvelle classe, et rendre cette map accessible à la partie logicielle qui se sert des factory, très certainement placée à un endroit pas pratique.

    Ca va finir avec une map inutile, des perfos consommées pour rien et certainement un horrible singleton. Je déconseille.

    La réponse qui a été validée par l'auteur sur le post stackoverflow correspond à ma propal.

  6. #6
    Membre éprouvé
    Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mars 2009
    Messages
    552
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mars 2009
    Messages : 552
    Points : 1 060
    Points
    1 060
    Par défaut
    Bonsoir,

    Je met mon grain de sel... C'est assez proche de l'exemple de la fabrique, mais plutôt que faire une map de fabrique déconnectée du reste; on fait une fabrique générique qui fait appel à des fabriques.

    L'initialisation de la map se passe dans le constructeur de la fabrique générique (=> pas de saleté statique).

    Quelques lignes pour être plus clair :

    Une hiérarchie de type

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
     
    class Item {};
     
    //on suppose >> definit sur ItemA et ItemB
    class ItemA : public Item {};
    class ItemB : public Item {};

    Des lecteurs pour types connus à la compilation

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
     
     
    /**
     * interface lecteur de ligne
     */
    class AbstractItemReader {
    public:
    	virtual ~AbstractItemReader();
     
    	virtual std::string getType() const = 0 ;	
    	virtual Item* readItem( std::istream & s ) = 0;
    };
     
    /**
     * Lecteur concret pour les lignes de type A
     */
    class ConcreteItemReaderA : public AbstractItemReader {
    public:
    	virtual std::string getType() const
    	{
    		return "ItemTypeA" ;
    	}
    	virtual Item* readItem( std::istream & s )
    	{
    		ItemA * itemA = new ItemA(); //todo auto_ptr
    		s >> (*itemA); //todo check
    		return itemA;
    	}
    };
     
     
    class ConcreteItemReaderB : public AbstractItemReader {
    public:
    	virtual std::string getType() const
    	{
    		return "ItemTypeB" ;
    	}
    	virtual Item* readItem( std::istream & s )
    	{
    		ItemB * itemB = new ItemB(); //todo auto_ptr
    		s >> (*itemB); //todo check
    		return itemB;
    	}
    };
    Un lecteur générique extensible

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
     
    /**
     * L'astuce est là : Un lecteur qui ne fait qu'appeler d'autres lecteurs
     * (je me demande s'il y a un DP pour nommé ça) 
     */
    class GenericReader : public AbstractItemReader {
    public:
    	GenericReader()
    	{
    		/* enregistre les lecteurs predefinis */
    		addReader( new ConcreteItemReaderA() );
    		addReader( new ConcreteItemReaderB() );
    	}
     
    	/* ajout d'un lecteur pour un type utilisateur */
    	void addReader( AbstractReader* reader )
    	{
    		//verifier existence et insulter...
    		_readers.insert( std::make_pair( reader->getType(), reader ) );		
    	}
     
    	/* lecture d'un element */
    	virtual Item * readItem( std::istream & s )
    	{
    		std::string itemType;
    		s >> itemType;
    		return getReaderByType().readItem( s );
    	}
     
    	AbstractReader & getReaderByType( std::string const& name );
     
    private:
    	/* map < id_reader, AbstractReader > */
    	std::map< std::string, AbstractReader* > _readers ;  
    };

    RQ :
    - Ca se généralise assez bien sur d'autres problèmes (lecture de fichier en fonction d'extension, on peut même charger dynamiquement un lecteur sous forme d'un plugin)
    - ConcreteItemReaderA peut être templatée avec une "interface statique" fournissant un nom de type ou encore une classe de trait.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     
    template < typename ItemType >
    class ConcreteItemReader : public AbstractItemReader {
    public:
    	virtual std::string getType() const
    	{
    		/* c'est là qu'on s'énerve sur le mangling dans 
    		std::typeid< ItemType >::name()...*/
    		return ItemType::Type();
    	}
    	virtual ItemType* readItem( std::istream & s )
    	{
    		ItemType * item = new ItemType(); //todo auto_ptr
    		s >> (*item); //todo check
    		return item;
    	}
    };

  7. #7
    Membre expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Points : 3 159
    Points
    3 159
    Par défaut
    Ce que je regrette dans cette propal, c'est qu'on en revient à celle décrite avant. Faire une correspondance de types runtime via une recherche dans une map. Peut importe quand est-ce que cette map est initialisée, on doit le faire et c'est dommage car pas utile.

    J'aimerais bien avoir l'avis de l'op, dommage qu'il ne repasse pas.

  8. #8
    Membre régulier
    Profil pro
    Inscrit en
    Avril 2008
    Messages
    87
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Avril 2008
    Messages : 87
    Points : 111
    Points
    111
    Par défaut
    Je ne suis pas sûr justement.
    S'il a besoin d'une factory c'est pour instancier des objets qui viennent d'une déserialization. Ou du moins du streaming de quelque chose qui décrit au minimum le type de l'objet.
    donc quelque soit cette manière de décrire le type dans le format sérialisé, il faudra bien mapper cette description a l'instance de factory necessaire pour instancier ce type.
    dans ton cas ce sont des indices, un indice ça map en O(1) mais ca map quand même.

  9. #9
    Membre expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Points : 3 159
    Points
    3 159
    Par défaut
    Citation Envoyé par Lightness1024 Voir le message
    S'il a besoin d'une factory c'est pour instancier des objets qui viennent d'une déserialization. Ou du moins du streaming de quelque chose qui décrit au minimum le type de l'objet.
    donc quelque soit cette manière de décrire le type dans le format sérialisé, il faudra bien mapper cette description a l'instance de factory necessaire pour instancier ce type.
    Alors ça, je suis d'accord, c'est une bonne raison. Espérons qu'[Hugo] repassera pour nous dire ce qu'il en est.

  10. #10
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Salut,

    Citation Envoyé par bretus Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    virtual std::string getType() const = 0 ;
    C'est le genre de ligne qui me choque rien qu'à la lecture avant même de voir à quoi elle sert . J'ai l'impression qu'on met en place un RTTI en // du RTTI.

    Ici, la clé n'a pas à être insérer dans la classe car elle est une information de la fabrique pas de la classe. En effet, il y aura une clé de type chaîne pour une fabrique à partir d'un fichier texte par exemple, mais je peux imaginer avoir une fabrique avec une clé binaire si j'ai une sérialisation binaire, etc...

    @jean-bernard : j'ai compris la question comme bretus et Lightness1024.

  11. #11
    Membre éprouvé
    Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mars 2009
    Messages
    552
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mars 2009
    Messages : 552
    Points : 1 060
    Points
    1 060
    Par défaut
    Bonjour,

    Juste remarque 3DArchi, il suffit de mettre sur la fabrique (GenericReader) un addReader( name, AbstractReader ) pour être moins intrusif...


    PS :

    - Si le RTTI disposait de typeid(T).name() sans mangling, on pourrait se reposer dessus pour de la serialization portable sans définir un nom portable (BOOST_CLASS_EXPORT est du même acabit que ce que j'écris)

    - Pour ce qui est de réinventer la roue carrée, on remet quasiment en place un mécanisme de chargement dynamique de classe à chaque fois que l'on fait ce genre de magouille (Considère GenericReader avec la possibilité de charger un plugin dans une bibliothèque partagée). Quand on en est à mettre en place des mécanismes qui devrait être assumé par le langage sur certaines classes qui font fasse à de la généricité non pas à la compilation mais à l'exécution, ça ne me choque pas de ponter le RTTI...

  12. #12
    Membre confirmé
    Profil pro
    Inscrit en
    Août 2006
    Messages
    620
    Détails du profil
    Informations personnelles :
    Âge : 47
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Août 2006
    Messages : 620
    Points : 453
    Points
    453
    Par défaut
    Bonjour à tous,

    Merci infiniment pour vos réponses, désolé de mon long silence, j'ai été out pour boulot et vacances... et je n'ai pas encore eu le temps de digérer toute l'info de vos posts !!

    Juste pour préciser ce qu'il se passe concrètement : c'est un code de calcul qui peut tourner pour différents type de lignes (pour le moment nous distinguons les types line et faultedline, mais d'autres sont à venir très bientôt). Ces lignes sont contenues dans des objets "template<class LineType> Graph" et les templates sont spécialisés suivnat les types de ligne. Les modélisations s'initialisent en lisant des xml, dont un contient la ligne suivante :
    qui détermine quel type de Graph doit être créé.
    Je ne sais pas si cela répond à la question de jblecanard et Lightness1024 ?

    En tout cas, encore une fois, un grand merci pour vos suggestions !!

    Meilleurs vœux à tous pour 2012.

    Marc

Discussions similaires

  1. template, singleton et object factory
    Par [Hugo] dans le forum Langage
    Réponses: 13
    Dernier message: 04/05/2009, 13h53
  2. Trouver le Type d'une classe template dynamiquement ?
    Par Serge Iovleff dans le forum Langage
    Réponses: 3
    Dernier message: 23/09/2005, 16h48
  3. [DLL/classe template] problème de link
    Par Bob.Killer dans le forum C++
    Réponses: 7
    Dernier message: 31/08/2005, 18h56
  4. Class template hérité
    Par Azharis dans le forum Langage
    Réponses: 4
    Dernier message: 24/06/2005, 22h03
  5. Réponses: 6
    Dernier message: 06/10/2004, 12h59

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo