2009-02-24 13 views
110

Ich habe eine Datei: base.hGibt es eine Möglichkeit, Objekte aus einer Zeichenfolge mit ihrem Klassennamen instanziieren?

class Base; 
class DerivedA : public Base; 
class DerivedB : public Base; 

/*etc...*/ 

und eine andere Datei: BaseFactory.h

#include "Base.h" 

class BaseFactory 
{ 
public: 
    BaseFactory(const string &sClassName){msClassName = sClassName;}; 

    Base * Create() 
    { 
    if(msClassName == "DerivedA") 
    { 
     return new DerivedA(); 
    } 
    else if(msClassName == "DerivedB") 
    { 
     return new DerivedB(); 
    } 
    else if(/*etc...*/) 
    { 
     /*etc...*/ 
    } 
    }; 
private: 
    string msClassName; 
}; 

/*etc.*/ 

Gibt es einen Weg, um diese Zeichenfolge zu einem tatsächlichen Typ (Klasse), um irgendwie zu konvertieren, so dass BaseFactory müsste nicht alle möglichen abgeleiteten Klassen kennen und hätte if() für jeden von ihnen? Kann ich aus dieser Zeichenfolge eine Klasse erstellen?

Ich denke, das kann in C# durch Reflexion getan werden. Gibt es in C++ etwas Ähnliches?

+0

seine teilweise möglich, mit C++ 0x und variadic templates .. – smerlin

Antwort

188

Nein, da ist keiner, es sei denn, Sie die Zuordnung selbst tun. C++ hat keinen Mechanismus zum Erstellen von Objekten, deren Typen zur Laufzeit bestimmt werden. Sie können eine Karte verwenden, um das selbst zu tun abbildet, aber:

template<typename T> Base * createInstance() { return new T; } 

typedef std::map<std::string, Base*(*)()> map_type; 

map_type map; 
map["DerivedA"] = &createInstance<DerivedA>; 
map["DerivedB"] = &createInstance<DerivedB>; 

Und dann können Sie

return map[some_string](); 

bekommen eine neue Instanz tun. Eine weitere Idee ist die Typen registrieren themself haben:

// in base.hpp: 
template<typename T> Base * createT() { return new T; } 

struct BaseFactory { 
    typedef std::map<std::string, Base*(*)()> map_type; 

    static Base * createInstance(std::string const& s) { 
     map_type::iterator it = getMap()->find(s); 
     if(it == getMap()->end()) 
      return 0; 
     return it->second(); 
    } 

protected: 
    static map_type * getMap() { 
     // never delete'ed. (exist until program termination) 
     // because we can't guarantee correct destruction order 
     if(!map) { map = new map_type; } 
     return map; 
    } 

private: 
    static map_type * map; 
}; 

template<typename T> 
struct DerivedRegister : BaseFactory { 
    DerivedRegister(std::string const& s) { 
     getMap()->insert(std::make_pair(s, &createT<T>)); 
    } 
}; 

// in derivedb.hpp 
class DerivedB { 
    ...; 
private: 
    static DerivedRegister<DerivedB> reg; 
}; 

// in derivedb.cpp: 
DerivedRegister<DerivedB> DerivedB::reg("DerivedB"); 

Sie könnten sich entscheiden, ein Makro für die ich bin sicher,

#define REGISTER_DEC_TYPE(NAME) \ 
    static DerivedRegister<NAME> reg 

#define REGISTER_DEF_TYPE(NAME) \ 
    DerivedRegister<NAME> NAME::reg(#NAME) 

dort Registrierung zu erstellen sind bessere Namen für diese beiden though. Eine andere Sache, die wahrscheinlich sinnvoll ist hier zu verwenden ist shared_ptr.

Wenn Sie eine Gruppe von nicht verwandten Typen haben, die keine gemeinsame Basisklasse haben, können Sie stattdessen dem Funktionszeiger den Rückgabetyp boost::variant<A, B, C, D, ...> geben. Wie, wenn Sie eine Klasse Foo, Bar und Baz haben, sieht es wie folgt aus:

typedef boost::variant<Foo, Bar, Baz> variant_type; 
template<typename T> variant_type createInstance() { 
    return variant_type(T()); 
} 

typedef std::map<std::string, variant_type (*)()> map_type; 

A boost::variant ist wie eine Vereinigung. Er weiß, welcher Typ darin gespeichert ist, indem er nach dem Objekt sucht, das für die Initialisierung oder Zuweisung verwendet wurde. Werfen Sie einen Blick auf die Dokumentation here. Schließlich ist auch die Verwendung eines Raw-Funktionszeigers etwas altmodisch. Moderner C++ - Code sollte von bestimmten Funktionen/Typen entkoppelt sein. Vielleicht möchten Sie in Boost.Function nach einem besseren Weg suchen. Es würde so aussehen dann (die Karte):

typedef std::map<std::string, boost::function<variant_type()> > map_type; 

std::function wird in der nächsten Version von C++ auch einschließlich std::shared_ptr zur Verfügung.

+3

Liebte die Idee, dass die abgeleiteten Klassen sich registrieren werden. Es ist genau das, wonach ich gesucht habe, eine Möglichkeit, das fest codierte Wissen, von dem abgeleitete Klassen existieren, aus der Fabrik zu entfernen. –

+1

Ursprünglich von Somedave in einer anderen Frage geschrieben, schlägt dieser Code auf VS2010 mit mehrdeutigen Vorlage Fehler wegen make_pair. Um das zu beheben, ändern Sie make_pair in std :: pair und es sollte diese Fehler beheben. Ich habe auch einige Verknüpfungsfehler, die behoben wurden, indem Sie BaseFactory :: map_type * BaseFactory :: map = new map_type(); zu base.cpp –

+0

Ich habe Probleme, dieses zu implementieren, Vs2010 verwendend und std :: pair anstelle von make_pair verwendend. Beim Kompilieren bekomme ich Linkfehler. Hinzufügen meines Gegenstücks zum statischen DerivedRegister reg, statisches MouseFeatureRegister mouse_reg; kompiliert gut. aber beim Hinzufügen von MouseFeatureRegister CvMaskOverlay :: mouse_reg ("Masking - Polygon"); Ich bekomme einige Linkfehler. Die Idee besteht nur darin, dass Unterklassen einen String in einer Karte mit einem ihm zugeordneten steigenden Zähler registrieren. also nicht mit Vorlagen –

6

Nein ist es nicht. Meine bevorzugte Lösung für dieses Problem besteht darin, ein Wörterbuch zu erstellen, das den Namen der Erstellungsmethode zuordnet. Klassen, die so erstellt werden sollen, registrieren dann eine Erstellungsmethode mit dem Wörterbuch. Dies wird im Detail in der GoF patterns book diskutiert.

+3

Jeder möchte herausfinden, welches Muster das ist, anstatt nur auf das Buch zu zeigen ? – josaphatv

+0

Ich denke, er bezieht sich auf das Registrierungsmuster. – jiggunjer

+1

Für diejenigen, die diese Antwort jetzt lesen, glaube ich, dass sich die Antwort auf das Factory-Muster bezieht, eine Implementierung, die ein Wörterbuch verwendet, um zu bestimmen, welche Klasse instanziiert werden soll. – Grimeh

0

Dies ist das Werksmuster. Siehe Wikipedia (und this Beispiel). Sie können keinen Typ per se aus einer Zeichenkette ohne einen ungeheuerlichen Hack erstellen. Warum brauchst du das?

+0

Ich brauche das, weil ich die Strings aus einer Datei lese, und wenn ich das habe, dann kann ich die Factory so generisch haben, dass sie nichts wissen müsste, um die richtige Instanz zu erstellen. Das ist sehr mächtig. –

+0

Also, sagst du, dass du keine unterschiedlichen Klassendefinitionen für einen Bus und ein Auto brauchst, da sie beide Fahrzeuge sind? Wenn Sie dies jedoch tun, sollte das Hinzufügen einer anderen Zeile nicht wirklich ein Problem sein :) Der Map-Ansatz hat das gleiche Problem - Sie aktualisieren den Karteninhalt. Das Makro-Ding funktioniert für triviale Klassen. – dirkgently

+0

Ich sage, dass, um einen Bus oder ein Auto in meinem Fall zu erstellen, brauche ich keine anderen Definitionen, sonst würde das Factory-Design-Muster nie verwendet werden. Mein Ziel war es, die Fabrik so dumm wie möglich zu machen. Aber ich sehe hier, dass es kein Entkommen gibt :-) –

4

Ich habe in einer anderen Frage SO über C++ - Fabriken geantwortet. Bitte sehen Sie there, wenn eine flexible Fabrik von Interesse ist. Ich versuche einen alten Weg von ET ++ zu beschreiben, um Makros zu verwenden, die für mich gut funktioniert haben.

ET++ war ein Projekt, um alte MacApp nach C++ und X11 zu portieren. In der Bemühung von ihm fing Eric Gamma usw. an, an zu denken Entwurfs-Muster

0

Tor Brede Vekterli bietet eine Boost-Erweiterung, die genau die Funktionalität bietet, die Sie suchen. Momentan ist es mit den aktuellen Boost-Bibliotheken etwas peinlich, aber ich konnte es mit 1.48_0 arbeiten lassen, nachdem ich seinen Basis-Namespace geändert hatte.

http://arcticinteractive.com/static/boost/libs/factory/doc/html/factory/factory.html#factory.factory.reference

In Antwort auf diejenigen, die sich fragen, warum so etwas (als Reflexion) würde für c nützlich sein ++ - Ich benutze es für Interaktionen zwischen der Benutzeroberfläche und einem Motor - der Benutzer eine Option in der Benutzeroberfläche auswählt, und die Engine nimmt die UI-Auswahlzeichenfolge und erzeugt ein Objekt des gewünschten Typs. Der Hauptvorteil der Verwendung des Frameworks hier (über die Beibehaltung einer Fruchtzeile) ist, dass die Registrierungsfunktion in jeder Klassendefinition ist (und nur eine Codezeile erfordert, die die Registrierungsfunktion pro registrierte Klasse aufruft) - im Gegensatz dazu zu einer Datei, die die Liste der Früchte enthält, die jedes Mal manuell hinzugefügt werden muss, wenn eine neue Klasse abgeleitet wird.

Ich machte die Fabrik ein statisches Mitglied meiner Basisklasse.

2

boost :: Funktions hat eine Fabrik Vorlage, die sehr flexibel ist: http://www.boost.org/doc/libs/1_54_0/libs/functional/factory/doc/html/index.html

obwohl Meine Präferenz ist Wrapper-Klassen zu erzeugen, die die Zuordnung und Objekterstellung Mechanismus verstecken. Das übliche Szenario, dem ich begegne, ist die Notwendigkeit, verschiedene abgeleitete Klassen einer Basisklasse Schlüsseln zuzuordnen, wobei die abgeleiteten Klassen alle eine gemeinsame Konstruktorsignatur zur Verfügung haben. Hier ist die Lösung, die ich bisher gefunden habe.

#ifndef GENERIC_FACTORY_HPP_INCLUDED 

//BOOST_PP_IS_ITERATING is defined when we are iterating over this header file. 
#ifndef BOOST_PP_IS_ITERATING 

    //Included headers. 
    #include <unordered_map> 
    #include <functional> 
    #include <boost/preprocessor/iteration/iterate.hpp> 
    #include <boost/preprocessor/repetition.hpp> 

    //The GENERIC_FACTORY_MAX_ARITY directive controls the number of factory classes which will be generated. 
    #ifndef GENERIC_FACTORY_MAX_ARITY 
     #define GENERIC_FACTORY_MAX_ARITY 10 
    #endif 

    //This macro magic generates GENERIC_FACTORY_MAX_ARITY + 1 versions of the GenericFactory class. 
    //Each class generated will have a suffix of the number of parameters taken by the derived type constructors. 
    #define BOOST_PP_FILENAME_1 "GenericFactory.hpp" 
    #define BOOST_PP_ITERATION_LIMITS (0,GENERIC_FACTORY_MAX_ARITY) 
    #include BOOST_PP_ITERATE() 

    #define GENERIC_FACTORY_HPP_INCLUDED 

#else 

    #define N BOOST_PP_ITERATION() //This is the Nth iteration of the header file. 
    #define GENERIC_FACTORY_APPEND_PLACEHOLDER(z, current, last) BOOST_PP_COMMA() BOOST_PP_CAT(std::placeholders::_, BOOST_PP_ADD(current, 1)) 

    //This is the class which we are generating multiple times 
    template <class KeyType, class BasePointerType BOOST_PP_ENUM_TRAILING_PARAMS(N, typename T)> 
    class BOOST_PP_CAT(GenericFactory_, N) 
    { 
     public: 
      typedef BasePointerType result_type; 

     public: 
      virtual ~BOOST_PP_CAT(GenericFactory_, N)() {} 

      //Registers a derived type against a particular key. 
      template <class DerivedType> 
      void Register(const KeyType& key) 
      { 
       m_creatorMap[key] = std::bind(&BOOST_PP_CAT(GenericFactory_, N)::CreateImpl<DerivedType>, this BOOST_PP_REPEAT(N, GENERIC_FACTORY_APPEND_PLACEHOLDER, N)); 
      } 

      //Deregisters an existing registration. 
      bool Deregister(const KeyType& key) 
      { 
       return (m_creatorMap.erase(key) == 1); 
      } 

      //Returns true if the key is registered in this factory, false otherwise. 
      bool IsCreatable(const KeyType& key) const 
      { 
       return (m_creatorMap.count(key) != 0); 
      } 

      //Creates the derived type associated with key. Throws std::out_of_range if key not found. 
      BasePointerType Create(const KeyType& key BOOST_PP_ENUM_TRAILING_BINARY_PARAMS(N,const T,& a)) const 
      { 
       return m_creatorMap.at(key)(BOOST_PP_ENUM_PARAMS(N,a)); 
      } 

     private: 
      //This method performs the creation of the derived type object on the heap. 
      template <class DerivedType> 
      BasePointerType CreateImpl(BOOST_PP_ENUM_BINARY_PARAMS(N,const T,& a)) 
      { 
       BasePointerType pNewObject(new DerivedType(BOOST_PP_ENUM_PARAMS(N,a))); 
       return pNewObject; 
      } 

     private: 
      typedef std::function<BasePointerType (BOOST_PP_ENUM_BINARY_PARAMS(N,const T,& BOOST_PP_INTERCEPT))> CreatorFuncType; 
      typedef std::unordered_map<KeyType, CreatorFuncType> CreatorMapType; 
      CreatorMapType m_creatorMap; 
    }; 

    #undef N 
    #undef GENERIC_FACTORY_APPEND_PLACEHOLDER 

#endif // defined(BOOST_PP_IS_ITERATING) 
#endif // include guard 

Ich bin generell gegen schwere Makro-Verwendung, aber ich habe hier eine Ausnahme gemacht. Der obige Code generiert GENERIC_FACTORY_MAX_ARITY + 1 Versionen einer Klasse mit dem Namen GenericFactory_N, für jede N zwischen 0 und GENERIC_FACTORY_MAX_ARITY inklusive.

Die Verwendung der generierten Klassenvorlagen ist einfach. Angenommen, Sie möchten eine Factory, die abgeleitete BaseClass-Objekte mithilfe einer Zeichenfolgenzuordnung erstellt. Jedes der abgeleiteten Objekte benötigt 3 ganze Zahlen als Konstruktorparameter.

#include "GenericFactory.hpp" 

typedef GenericFactory_3<std::string, std::shared_ptr<BaseClass>, int, int int> factory_type; 

factory_type factory; 
factory.Register<DerivedClass1>("DerivedType1"); 
factory.Register<DerivedClass2>("DerivedType2"); 
factory.Register<DerivedClass3>("DerivedType3"); 

factory_type::result_type someNewObject1 = factory.Create("DerivedType2", 1, 2, 3); 
factory_type::result_type someNewObject2 = factory.Create("DerivedType1", 4, 5, 6); 

Der Klassen-Destruktor GenericFactory_N ist virtuell, um Folgendes zu ermöglichen.

class SomeBaseFactory : public GenericFactory_2<int, BaseType*, std::string, bool> 
{ 
    public: 
     SomeBaseFactory() : GenericFactory_2() 
     { 
      Register<SomeDerived1>(1); 
      Register<SomeDerived2>(2); 
     } 
}; 

SomeBaseFactory factory; 
SomeBaseFactory::result_type someObject = factory.Create(1, "Hi", true); 
delete someObject; 

Beachten Sie, dass diese Zeile des generischen Fabrik Generator Makro

#define BOOST_PP_FILENAME_1 "GenericFactory.hpp" 

nimmt die generische Werk Header-Datei benannt wird GenericFactory.hpp

1
--------------- 
Detail solution for registering the objects, and accessing them with string names. 
--------------- 
1. common.h 
#ifndef COMMON_H_ 
#define COMMON_H_ 


#include<iostream> 
#include<string> 
#include<iomanip> 
#include<map> 

using namespace std; 
class Base{ 
public: 
    Base(){cout <<"Base constructor\n";} 
    virtual ~Base(){cout <<"Base destructor\n";} 
}; 
#endif /* COMMON_H_ */ 

2. test1.h 
/* 
* test1.h 
* 
* Created on: 28-Dec-2015 
*  Author: ravi.prasad 
*/ 

#ifndef TEST1_H_ 
#define TEST1_H_ 
#include "common.h" 

class test1: public Base{ 
    int m_a; 
    int m_b; 
public: 
    test1(int a=0, int b=0):m_a(a),m_b(b) 
    { 
     cout <<"test1 constructor m_a="<<m_a<<"m_b="<<m_b<<endl; 
    } 
    virtual ~test1(){cout <<"test1 destructor\n";} 
}; 



#endif /* TEST1_H_ */ 

3. test2.h 
#ifndef TEST2_H_ 
#define TEST2_H_ 
#include "common.h" 

class test2: public Base{ 
    int m_a; 
    int m_b; 
public: 
    test2(int a=0, int b=0):m_a(a),m_b(b) 
    { 
     cout <<"test1 constructor m_a="<<m_a<<"m_b="<<m_b<<endl; 
    } 
    virtual ~test2(){cout <<"test2 destructor\n";} 
}; 


#endif /* TEST2_H_ */ 

3. main.cpp 
#include "test1.h" 
#include "test2.h" 

template<typename T> Base * createInstance(int a, int b) { return new T(a,b); } 

typedef std::map<std::string, Base* (*)(int,int)> map_type; 

map_type mymap; 

int main() 
{ 

    mymap["test1"] = &createInstance<test1>; 
    mymap["test2"] = &createInstance<test2>; 

    /*for (map_type::iterator it=mymap.begin(); it!=mymap.end(); ++it) 
     std::cout << it->first << " => " << it->second(10,20) << '\n';*/ 

    Base *b = mymap["test1"](10,20); 
    Base *b2 = mymap["test2"](30,40); 

    return 0; 
} 

------------------------ 
Compile and Run it (Have done this with Eclipse) 
------------------------ 
/Output 

Base constructor 
test1 constructor m_a=10m_b=20 
Base constructor 
test1 constructor m_a=30m_b=40