C'è un modo per creare un'istanza di oggetti da una stringa che contiene il nome della loro classe?


143

Ho un file: Base.h

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 questa stringa in un tipo (classe) effettivo, in modo che BaseFactory non debba conoscere tutte le possibili classi derivate e avere if () per ognuna di esse? Posso produrre una classe da questa stringa?

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


è parzialmente possibile con C ++ 0x e modelli variadici ..
smerlin,

Risposte:


227

No, non ce n'è, a meno che tu non faccia da solo la mappatura. C ++ non ha alcun meccanismo per creare oggetti i cui tipi sono determinati in fase di esecuzione. Puoi usare una mappa per farlo tu stesso, 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 poi puoi farlo

return map[some_string]();

Ottenere una nuova istanza. Un'altra idea è di avere i tipi registrati da soli:

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

Potresti 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 nomi migliori 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 assegnare al puntatore a funzione un tipo restituito boost::variant<A, B, C, D, ...>. Come se avessi una classe Foo, Bar e Baz, sembra così:

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è come un'unione. Sa quale tipo è memorizzato in esso cercando quale oggetto è stato usato per inizializzarlo o assegnarlo. Dai un'occhiata alla sua documentazione qui . Infine, anche l'uso di un puntatore a funzione grezza è un po 'vecchio. Il codice C ++ moderno dovrebbe essere disaccoppiato da funzioni / tipi specifici. Potresti voler esaminare Boost.Functionper cercare un modo migliore. Sembrerebbe quindi questo (la mappa):

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

std::functionsarà disponibile anche nella prossima versione di C ++, incluso std::shared_ptr.


3
Mi è piaciuta l'idea che le classi derivate si registrino da sole. È esattamente quello che stavo cercando, un modo per rimuovere dalla fabbrica la conoscenza codificata delle classi derivate.
Gal Goldman,

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 risolvere, cambia make_pair in std :: pair <std :: string, Base * ( ) ()> e dovrebbe correggere quegli errori. Ho anche riscontrato alcuni errori di collegamento che sono stati corretti aggiungendo BaseFactory :: map_type BaseFactory :: map = new map_type (); a base.cpp
Spencer Rose,

9
Come si garantisce che DerivedB::regsia effettivamente inizializzato? La mia comprensione è che potrebbe non essere affatto costruito se nessuna funzione o oggetto definito nell'unità di traduzione derivedb.cpp, come in 3.6.2.
musiphil

2
Adoro l'auto-registrazione. Per compilare anche se avevo bisogno di un BaseFactory::map_type * BaseFactory::map = NULL;nel mio file cpp. Senza questo, il linker si è lamentato della mappa di simboli sconosciuti.
Sven,

1
Sfortunatamente, questo non funziona. Come già sottolineato da musiphil, DerivedB::regnon viene inizializzato se nessuna delle sue funzioni o istanze è definita nell'unità di traduzione derivedb.cpp. Ciò significa che la classe non viene registrata fino a quando non viene effettivamente istanziata. Qualcuno sa una soluzione per questo?
Tomasito665,

7

No non c'è. La mia soluzione preferita a questo problema è creare un dizionario che associ 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 nel libro dei modelli GoF .


5
Qualcuno si preoccupa di identificare quale modello sia, piuttosto che indicare semplicemente il libro?
josaphatv,

Penso che si stia riferendo al modello di registro.
jiggunjer,

2
Per coloro che leggono questa risposta ora, credo che la risposta si riferisca all'utilizzo del modello Factory, un'implementazione che utilizza un dizionario per determinare quale classe creare un'istanza.
Grimeh,


4

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

ET ++ era un progetto per il porting della vecchia MacApp su C ++ e X11. Nello sforzo di esso Eric Gamma ecc ha iniziato a pensare a Design Patterns


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ò quella di generare classi wrapper che nascondano il meccanismo di mappatura e creazione degli oggetti. Lo scenario comune che incontro è la necessità di associare diverse classi derivate di alcune classi base alle chiavi, in cui tutte le classi derivate dispongono di una firma costruttore comune. 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

Sono generalmente contrario al forte uso di macro, ma ho fatto un'eccezione qui. Il codice precedente genera GENERIC_FACTORY_MAX_ARITY + 1 versioni di una classe denominata GenericFactory_N, per ogni N compresa tra 0 e GENERIC_FACTORY_MAX_ARITY inclusi.

L'uso dei modelli di classe generati è semplice. Supponiamo che tu voglia che una fabbrica crei oggetti derivati ​​BaseClass usando una mappatura di stringhe. Ciascuno degli oggetti derivati ​​accetta 3 numeri 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 riga della macro generica del generatore di fabbrica

#define BOOST_PP_FILENAME_1 "GenericFactory.hpp"

Presuppone che il file di intestazione di fabbrica generico sia denominato GenericFactory.hpp


2

Soluzione dettagliata per la registrazione degli oggetti e l'accesso ad essi con nomi di stringhe.

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_ */

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_ */

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

Compilalo ed eseguilo (lo hai fatto con Eclipse)

Produzione:

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


1

Tor Brede Vekterli offre un'estensione boost che offre esattamente la funzionalità che cerchi. Attualmente, è leggermente scomodo adattarsi alle attuali librerie boost, ma sono stato in grado di 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 si chiedono perché una cosa del genere (come riflessione) sarebbe utile per c ++ - la utilizzo per interazioni tra l'interfaccia utente e un motore - l'utente seleziona un'opzione nell'interfaccia utente e il motore accetta la stringa di selezione dell'interfaccia utente, e produce un oggetto del tipo desiderato.

Il principale vantaggio dell'utilizzo del framework qui (rispetto al mantenimento di una lista di frutti da qualche parte) è che la funzione di registrazione è nella definizione di ciascuna classe (e richiede solo una riga di codice che chiama la funzione di registrazione per classe registrata) - al contrario di un file contenente l'elenco dei frutti, che deve essere aggiunto manualmente ogni volta che viene derivata una nuova classe.

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


0

Questo è il modello di fabbrica. Vedi Wikipedia (e questo esempio). Non puoi creare un tipo di per sé da una stringa senza qualche hack egregio. Perchè ti serve?


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

Quindi, stai dicendo che non avrai bisogno di definizioni di classe diverse per un autobus e un'auto poiché sono entrambi veicoli? Tuttavia, se lo fai, l'aggiunta di un'altra riga non dovrebbe essere un problema :) L'approccio della mappa ha lo stesso problema: aggiorni il contenuto della mappa. La cosa macro funziona per banali classi.
diretto il

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 avere la fabbrica più stupida che potesse essere. Ma vedo qui che non c'è scampo :-)
Gal Goldman,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.