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++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé
    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
    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 éprouvé
    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
    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
    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 éprouvé
    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
    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
    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 émérite
    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
    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;
    	}
    };

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