2009-02-24 8 views
110

Tengo un archivo: Base.h¿Hay alguna manera de crear instancias de objetos a partir de una cadena que contenga su nombre de clase?

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

/*etc...*/ 

y otro archivo: 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.*/ 

¿Hay una manera de convertir de alguna manera esta cadena a un tipo real (clase), de manera que BaseFactory no debería conocer todas las posibles clases derivadas, y tener if() para cada una de ellas. ¿Puedo producir una clase de esta cadena?

Creo que esto se puede hacer en C# mediante Reflection. ¿Hay algo similar en C++?

+0

su parte es posible con C++ 0x y plantillas variadic .. – smerlin

Respuesta

188

No, no hay ninguno, a menos que realice el mapeo sí mismo. C++ no tiene mecanismo para crear objetos cuyos tipos se determinen en tiempo de ejecución. Se puede usar un mapa para hacer que el mapeo de sí mismo, sin embargo:

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

Y entonces usted puede hacer

return map[some_string](); 

Obtención de una nueva instancia. Otra idea es tener los tipos registrarse por si mismos:

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

Se podría decidir la creación de una macro para el registro

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

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

Estoy seguro de que hay mejores nombres para los dos sin embargo. Otra cosa que probablemente tenga sentido usar aquí es shared_ptr.

Si tiene un conjunto de tipos no relacionados que no tienen una clase base común, puede darle al puntero de la función un tipo de retorno de boost::variant<A, B, C, D, ...>. Al igual que si usted tiene una clase Foo, Bar y Baz, que se parece a esto:

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 es como una unión. Sabe qué tipo se almacena buscando el objeto que se utilizó para inicializarlo o asignarlo. Eche un vistazo a su documentación here. Finalmente, el uso de un puntero de función sin procesar también es un poco anticuado. El código moderno de C++ debe estar desacoplado de funciones/tipos específicos. Es posible que desee mirar en Boost.Function para buscar una mejor manera. Se vería como esto entonces (el mapa):

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

std::function estará disponible en la próxima versión de C++ también, incluyendo std::shared_ptr.

+3

Me encantó la idea de que las clases derivadas se registren. Es exactamente lo que estaba buscando, una forma de eliminar el conocimiento codificado de las clases derivadas que existen en la fábrica. –

+1

Publicado originalmente por somedave en otra pregunta, este código falla en VS2010 con errores de plantilla ambiguos debido a make_pair. Para solucionarlo, cambie make_pair a std :: pair y debería corregir esos errores. También recibí algunos errores de enlace que se corrigieron añadiendo BaseFactory :: map_type * BaseFactory :: map = new map_type(); a base.cpp –

+0

Tengo problemas para implementar esto, usando Vs2010 y usando std :: pair en lugar de make_pair. Al compilar recibo errores de enlace. Agregar mi homólogo a DerivedRegister estático reg, estático MouseFeatureRegister mouse_reg; compila bien pero al agregar MouseFeatureRegister CvMaskOverlay :: mouse_reg ("Enmascaramiento - Polígono"); obtengo algunos errores de enlace. La idea es que las subclases registren una cadena en un mapa con un contador creciente asignado. entonces no usar plantillas –

6

n no lo hay. Mi solución preferida para este problema es crear un diccionario que asigna el nombre al método de creación. Las clases que desean crearse así registran un método de creación con el diccionario. Esto se discute con cierto detalle en el GoF patterns book.

+3

A nadie le importa identificar qué patrón es este, en lugar de simplemente señalar el libro ? – josaphatv

+0

Creo que se está refiriendo al patrón de registro. – jiggunjer

+1

Para aquellos que lean esta respuesta ahora, creo que la respuesta se refiere al uso del patrón Factory, una implementación que usa un diccionario para determinar qué clase instanciar. – Grimeh

0

Este es el patrón de fábrica. Ver wikipedia (y this ejemplo). No puede crear un tipo per se a partir de una cadena sin algún corte atroz. ¿Por qué necesitas esto?

+0

Necesito esto porque leo las cadenas de un archivo, y si tengo esto, entonces puedo tener la fábrica tan genérica, que no tendría que saber nada para crear la instancia correcta. Esto es muy poderoso –

+0

Entonces, ¿está diciendo que no necesitará definiciones de clases diferentes para un Autobús y un Auto ya que ambos son Vehículos? Sin embargo, si lo hace, agregar otra línea no debería ser realmente un problema :) El enfoque del mapa tiene el mismo problema: actualiza los contenidos del mapa. La macro cosa funciona para clases triviales. – dirkgently

+0

Digo que para CREAR un Autobús o un Automóvil en mi caso, no necesito definiciones diferentes, de lo contrario, el patrón de diseño de Fábrica nunca estaría en uso. Mi objetivo era tener la fábrica tan estúpida como sea posible. Pero veo aquí que no hay escapatoria :-) –

4

He respondido en otra pregunta sobre las fábricas C++. Consulte there si una fábrica flexible es de su interés. Intento describir una antigua forma de ET ++ para usar macros, que me ha funcionado muy bien.

ET++ fue un proyecto para portar MacApp antiguo a C++ y X11. En el esfuerzo de él Eric Gamma etc. comenzó a pensar en Patrones de diseño

0

Tor Brede Vekterli proporciona una extensión de impulso que proporciona exactamente la funcionalidad que busca. Actualmente, es un poco incómodo con las librerías de impulso actuales, pero pude hacerlo funcionar con 1.48_0 después de cambiar su espacio de nombres base.

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

En respuesta a los que cuestionan por qué tal cosa (como reflexión) sería útil para C++ - Yo lo uso para las interacciones entre la interfaz de usuario y un motor - el usuario selecciona una opción en la interfaz de usuario, y el motor toma la cadena de selección de UI y produce un objeto del tipo deseado. El beneficio principal de usar el marco aquí (sobre mantener una lista de resultados en alguna parte) es que la función de registro está en la definición de cada clase (y solo requiere una línea de código que llame a la función de registro por clase registrada), en oposición a un archivo que contiene la lista de la fruta, que debe agregarse manualmente a cada vez que se deriva una nueva clase.

Hice de fábrica un miembro estático de mi clase base.

2

impulso :: funcional tiene una plantilla de fábrica que es bastante flexible: http://www.boost.org/doc/libs/1_54_0/libs/functional/factory/doc/html/index.html

Mi preferencia es sin embargo para generar clases contenedoras que ocultan el mecanismo de mapeo y la creación de objetos. El escenario común que encuentro es la necesidad de asignar diferentes clases derivadas de algunas clases base a claves, donde todas las clases derivadas tienen una firma de constructor común disponible. Esta es la solución que he encontrado hasta ahora.

#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 

En general, me opongo al macro uso pesado, pero he hecho una excepción aquí. El código anterior genera GENERIC_FACTORY_MAX_ARITY + 1 versiones de una clase llamada GenericFactory_N, para cada N entre 0 y GENERIC_FACTORY_MAX_ARITY inclusive.

Usar las plantillas de clase generadas es fácil. Supongamos que desea que una fábrica cree objetos derivados de BaseClass utilizando una asignación de cadena. Cada uno de los objetos derivados toma 3 enteros como parámetros de constructor.

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

El destructor de la clase GenericFactory_N es virtual para permitir lo siguiente.

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; 

Tenga en cuenta que esta línea de la fábrica generador genérico macro

#define BOOST_PP_FILENAME_1 "GenericFactory.hpp" 

asume que el archivo de cabecera de fábrica genérica se denomina 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 
Cuestiones relacionadas