2010-09-08 3 views
2

Je veux créer une usine de classe et j'aimerais utiliser la réflexion pour cela. Je ai juste besoin de créer un objet avec une chaîne donnée et d'appeler seulement quelques méthodes connues.réflexion minimale en C++

Comment je peux faire ça?

+2

S'il vous plaît, pourriez-vous fournir un pseudo code de ce que vous attendez réellement? Il n'y a aucun moyen de vous aider ici. –

+1

A quoi ressemble la réflexion en C++? – Gabe

+1

@Gabe: Il n'y a pas de réflexion en C++, il existe différentes approches pour simuler la réflexion mais elle n'est pas fournie par le langage. –

Répondre

3

Vous devrez rouler les vôtres. Habituellement, vous avez une carte de chaînes pour les fonctions de création d'objets.
Vous aurez besoin quelque chose comme le follwing:

class thing {...}; 
/* 
class thing_A : public thing {...}; 
class thing_B : public thing {...}; 
class thing_C : public thing {...}; 
*/ 

std::shared_ptr<thing> create_thing_A(); 
std::shared_ptr<thing> create_thing_C(); 
std::shared_ptr<thing> create_thing_D(); 

namespace { 
    typedef std::shared_ptr<thing> (*create_func)(); 

    typedef std::map<std::string,create_func> creation_map; 
    typedef creation_map::value_type creation_map_entry; 
    const creation_map_entry creation_map_entries[] = { {"A", create_thing_A} 
                , {"B", create_thing_B} 
                , {"C", create_thing_C} }; 
    const creation_map creation_funcs( 
      creation_map_entries, 
      creation_map_entries + sizeof(creation_map_entries) 
          /sizeof(creation_map_entries[0]); 
} 

std::shared_ptr<thing> create_thing(const std::string& type) 
{ 
    const creation_ma::const_iterator it = creation_map.find(type); 
    if(it == creation_map.end()) { 
    throw "Dooh!"; // or return NULL or whatever suits you 
    } 
    return it->second(); 
} 

Il y a d'autres façons de le faire (comme avoir a map of strings to objects from which to clone), mais je pense qu'ils se résument toutes à avoir une carte de chaînes à quelque chose lié aux types spécifiques .

+0

Le problème de votre approche est que vous devez connaître l'ensemble des classes au moment de la compilation, n'est-ce pas? – chiccodoro

+0

@chiccodoro: Il n'y a pas grand chose d'autre à faire en C++. Une autre façon (qui est, je suppose, que les langages font cela avec la réflexion intégrée) est de mettre des choses spéciales dans toutes les classes qui les enregistrent automatiquement avec un mécanisme de réflexion. Vous pouvez, par exemple, demander à toutes les classes d'enregistrer une méthode de fabrique de membres statique avec une certaine chaîne. Fondamentalement, cela se résumerait à ce qui précède, seulement avec une mécanique un peu plus élaborée qui permet d'ajouter des classes qui n'étaient pas connues lorsque le mécanisme a été mis en place. – sbi

+0

@sbi n'est-ce pas ce à quoi sert ce typeid? –

2

Il n'y a pas de réflexion en C++, directement supportée par la norme.

Cependant, C++ est suffisamment bas niveau pour que vous puissiez implémenter un minimum de support pour la réflexion pour terminer la tâche en cours.

Pour la simple tâche de création d'une usine, vous utilisez habituellement l'approche Prototype:

class Base 
{ 
public: 
    virtual Base* clone() const = 0; 
    virtual ~Base(); 
}; 

class Factory 
{ 
public: 
    std::unique_ptr<Base> get(std::string const& name); 

    void set(std::string const& name, std::unique_ptr<Base> b); 

private: 
    boost::ptr_map<std::string,Base> mExemplars; 
}; 

Bien sûr, ces « méthodes connues » que vous parlez devraient être définies dans la classe Base, qui agit comme une interface.

+0

Oui, le clonage d'exemples est un autre moyen de le faire. '+ 1' de moi. – sbi

1

Il n'y a pas de réflexion en C++, vous devez donc reformuler votre question en essayant d'expliquer quelles sont les exigences que vous auriez remplies avec la partie réflexion. En fonction de vos contraintes et besoins réels, il y a plusieurs choses que vous pouvez faire. La première approche que je prendrais serait la création d'une usine abstraite où les usines de béton peuvent enregistrer et fournir une interface simple:

class Base {}; // shared base by all created objects 
class ConcreteFactoryBase { 
public: 
    virtual ~ConcreteFactoryBase() {} 
    virtual Base* create() const = 0; // actual construction 
    virtual std::string id() const = 0; // id of the types returned 
}; 
class AbstractFactory 
{ 
    typedef std::map<std::string, ConcreteFactory* > factory_map_t; 
public: 
    void registerFactory(ConcreteFactoryBase* factory) { 
     factories[ factory->id() ] = factory; 
    } 
    Base* create(std::string const & id) const { 
     factory_map_t::const_iterator it = factories.find(id); 
     if (it == factories.end()) { 
     return 0; // or throw, or whatever makes sense in your case 
     } 
     return (*it)->create(); 
    } 
    ~AbstractFactory(); // ensure that the concrete factories are deleted 
private: 
    std::map<ConcreteFactoryBase*> factories; 
}; 

Les usines de béton réelles peuvent être mises en œuvre manuellement mais ils peuvent probablement être basés sur des modèles, à moins que les constructeurs pour les différents types aient des arguments différents:

template <typename T> 
class ConcreteFactory : public ConcreteFactoryBase { 
public: 
    ConcreteFactory(std::string const & id) : myid(id) {} 
    virtual Base* create() const { 
     return new T; 
    } 
    virtual std::string id() const { 
     return myid; 
    } 
private: 
    std::string myid; 
}; 
class Test : public Base {}; 
int main() { 
    AbstracFactory factory; 
    factory.register_factory(new ConcreteFactory<Test>("Test")); 
} 

En option, vous pourrait adapter les signatures afin que vous puissiez passer des arguments au constructeur à travers les différentes couches.

Ensuite, en connaissant les contraintes réelles, d'autres approches pourraient être meilleures. L'approche clone() suggérée ailleurs est bonne (soit en clonant réellement ou en créant un objet vide du même type). C'est fondamentalement mélanger l'usine avec les objets eux-mêmes de sorte que chaque objet est une usine d'objets du même type. Je n'aime pas vraiment mélanger ces deux responsabilités mais c'est peut-être l'une des approches les plus simples avec moins de code à écrire.

0

Vous pouvez utiliser des modèles typeid & pour implémenter l'usine, vous n'aurez donc pas besoin de chaînes du tout.

#include <string> 
#include <map> 
#include <typeinfo> 

//***** Base ***** 
class Base 
{ 
public: 
    virtual ~Base(){} //needs to be virtual to make typeid work 
}; 

//***** C1 ***** 
class C1 : public Base 
{}; 

//***** Factory ***** 
class Factory 
{ 
public: 
    template <class T> 
    Base& get(); 
private: 
    typedef std::map<std::string, Base> BaseMap; 
    BaseMap m_Instances; 
}; 

template <class T> 
Base& Factory::get() 
{ 
    BaseMap::const_iterator i = m_Instances.find(typeid(T).name()); 
    if(i == m_Instances.end()) { 
     m_Instances[typeid(T).name()] = T(); 
    } 
    return m_Instances[typeid(T).name()]; 
} 

//***** main ***** 
int main(int argc, char *argv[]) 
{ 
    Factory f; 
    Base& c1 = f.get<C1>(); 
    return 0; 
}