2009-02-24 4 views
110

Ho un file: Base.hEsiste un modo per creare un'istanza di oggetti da una stringa che contiene il nome della classe?

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

/*etc...*/ 

e un altro file: 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.*/ 

C'è un modo per convertire in qualche modo questo stringa in un tipo effettivo (classe), in modo che BaseFactory non dovrebbe conoscere tutte le possibili classi derivate e avere if() per ognuna di esse? Posso produrre una classe da questa stringa?

Penso che questo può essere fatto in C# tramite Reflection. C'è qualcosa di simile in C++?

+0

sua parte possibile con C++ 0x e modelli variadici .. – smerlin

risposta

188

No, non c'è nessuno, a meno che non la mappatura da soli. C++ non ha alcun meccanismo per creare oggetti i cui tipi sono determinati in fase di runtime. È possibile utilizzare una mappa per farlo mappatura da soli, però:

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

E allora si può fare

return map[some_string](); 

Ottenere una nuova istanza. Un'altra idea è quella di avere i tipi registrano themself:

// 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"); 

Si potrebbe decidere di creare una macro per la registrazione

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

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

Sono sicuro che ci sono più nomi per quei due però. Un'altra cosa che probabilmente ha senso usare qui è shared_ptr.

Se si dispone di un set di tipi non correlati che non hanno una classe base comune, è possibile fornire al puntatore funzione un tipo di ritorno di boost::variant<A, B, C, D, ...>. Come se si dispone di una classe Foo, Bar e Baz, sembra che questo:

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; 

Un boost::variant è come un'unione. Conosce il tipo in cui è memorizzato, osservando quale oggetto è stato utilizzato per inizializzarlo o assegnarlo. Dai un'occhiata alla sua documentazione here. Infine, anche l'uso di un puntatore a funzione non elaborata è un po 'obsoleto. Il codice C++ moderno dovrebbe essere disaccoppiato da funzioni/tipi specifici. Si consiglia di esaminare Boost.Function per cercare un modo migliore. Sarebbe simile a questa, allora (mappa):

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

std::function sarà disponibile nella prossima versione di C++ anche, compreso std::shared_ptr.

+3

Mi è piaciuta l'idea che le classi derivate si registreranno da sole. È esattamente quello che stavo cercando, un modo per rimuovere la conoscenza hard-coded di quali classi derivate esistono dalla fabbrica. –

+1

Originariamente pubblicato da somedave in un'altra domanda, questo codice non riesce su VS2010 con errori di modello ambigui a causa di make_pair. Per correggere, modifica make_pair a std :: pair e dovrebbe correggere quegli errori. Ho anche ricevuto degli errori di collegamento che sono stati risolti aggiungendo BaseFactory :: map_type * BaseFactory :: map = new map_type(); to base.cpp –

+0

Ho problemi nell'implementare questo, usando Vs2010 e usando std :: pair invece di make_pair. Durante la compilazione ricevo errori di collegamento. Aggiunta della mia controparte a DerivedRegister statico reg, MouseFeatureRegister statico mouse_reg; compila bene. ma quando aggiungi MouseFeatureRegister CvMaskOverlay :: mouse_reg ("Masking - Polygon"); Ottengo alcuni errori di collegamento. L'idea è solo per le sottoclassi di registrare una stringa in una mappa con un contatore crescente assegnato ad essa. quindi non usare i modelli –

6

No non c'è. La mia soluzione preferita a questo problema è creare un dizionario che mappa il nome al metodo di creazione. Le classi che vogliono essere create in questo modo quindi registrano un metodo di creazione con il dizionario. Questo è discusso in dettaglio nello GoF patterns book.

+3

A qualcuno interessa identificare quale modello è, piuttosto che puntare semplicemente al libro ? – josaphatv

+0

Penso che si riferisca al modello di registro. – jiggunjer

+1

Per coloro che stanno leggendo questa risposta ora, credo che la risposta si riferisca all'uso del pattern Factory, un'implementazione che usa un dizionario per determinare quale classe istanziare. – Grimeh

0

Questo è il modello di fabbrica. Vedi wikipedia (e esempio this). Non è possibile creare un tipo di per sé da una stringa senza alcun trucco egregio. Perchè ti serve?

+0

Ho bisogno di questo perché leggo le stringhe da un file, e se ho questo, allora posso avere la factory così generica, che non dovrebbe sapere nulla per creare l'istanza giusta. Questo è molto potente. –

+0

Quindi, stai dicendo che non avrai bisogno di definizioni di classi diverse per un autobus e una macchina dato che sono entrambi veicoli? Tuttavia, se lo fai, aggiungere un'altra linea non dovrebbe essere un problema :) L'approccio della mappa ha lo stesso problema: tu aggiorni il contenuto della mappa. La cosa macro funziona per classi banali. – dirkgently

+0

Sto dicendo che per creare un autobus o un'auto nel mio caso, non ho bisogno di definizioni diverse, altrimenti il ​​modello di progettazione di fabbrica non sarebbe mai in uso. Il mio obiettivo era quello di rendere la fabbrica la più stupida possibile. Ma vedo qui che non c'è via di fuga :-) –

4

Ho risposto a un'altra domanda SO sulle fabbriche C++. Si prega di vedere there se una fabbrica flessibile è di interesse. Cerco di descrivere un vecchio modo da ET ++ per usare macro che ha funzionato alla grande per me.

ET++ era un progetto per eseguire il porting della vecchia MacApp in C++ e X11. Eric Gamma ecc ha iniziato a pensare a Design Patterns

0

Tor Brede Vekterli fornisce un'estensione boost che fornisce esattamente la funzionalità che si cerca. Attualmente, è leggermente allettante con le librerie di boost correnti, ma sono riuscito a farlo funzionare con 1.48_0 dopo aver cambiato il suo spazio dei nomi di base.

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

In risposta a coloro che mettono in dubbio il motivo per cui una cosa del genere (come riflessione) sarebbe utile per C++ - Io lo uso per le interazioni tra l'interfaccia utente e un motore - l'utente seleziona un'opzione nell'interfaccia utente, e il motore prende la stringa di selezione dell'interfaccia utente e produce un oggetto del tipo desiderato.

Il vantaggio principale di utilizzare il framework qui (mantenendo un elenco di frutta da qualche parte) è che la funzione di registrazione è nella definizione di ogni classe (e richiede solo una riga di codice che chiama la funzione di registrazione per classe registrata) - al contrario in un file che contiene la lista di frutta, che deve essere aggiunta manualmente ogni volta che viene derivata una nuova classe.

Ho reso la fabbrica un membro statico della mia classe base.

2

boost :: funzionale ha un modello di fabbrica che è abbastanza flessibile: http://www.boost.org/doc/libs/1_54_0/libs/functional/factory/doc/html/index.html

La mia preferenza è però di generare classi wrapper che nascondono il meccanismo di mappatura e la creazione di oggetti. Lo scenario comune che incontro è la necessità di mappare le diverse classi derivate di alcune classi base alle chiavi, in cui tutte le classi derivate dispongono di una firma comune del costruttore. Ecco la soluzione che ho trovato finora.

#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 

sto generalmente contraria all'uso macro pesante, ma ho fatto un'eccezione qui. Il codice precedente genera GENERIC_FACTORY_MAX_ARITY + 1 versione di una classe denominata GenericFactory_N, per ogni N tra 0 e GENERIC_FACTORY_MAX_ARITY inclusi.

L'utilizzo dei modelli di classe generati è semplice. Supponiamo che vogliate una fabbrica per creare oggetti derivati ​​BaseClass usando una mappatura delle stringhe. Ciascuno degli oggetti derivati ​​prende 3 interi come parametri del costruttore.

#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); 

Il distruttore di classe GenericFactory_N è virtuale per consentire quanto segue.

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; 

Si noti che questa linea del generatore di fabbrica generico macro

#define BOOST_PP_FILENAME_1 "GenericFactory.hpp" 

presuppone che il file di intestazione fabbrica generico è chiamato 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 
Problemi correlati