¿Hay una manera de crear instancias de objetos de una cadena que contiene el nombre de la clase?

StackOverflow https://stackoverflow.com/questions/582331

Pregunta

Tengo un archivo: Base.h

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 tendría que conocer todas las posibles clases derivadas, y tienen if () para cada uno de ellos? ¿Puedo producir una clase de esta cadena?

Creo que esto se puede hacer en C # mediante la reflexión. ¿Hay algo similar en C ++?

¿Fue útil?

Solución

No, no hay ninguno, a menos que realice el mapeo sí mismo. C ++ no tiene ningún mecanismo para crear objetos cuyos tipos se determine 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 a continuación, 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 tiene sentido utilizar aquí es shared_ptr.

Si usted tiene un conjunto de tipos no relacionados que no tienen ninguna clase base común, usted puede hacer la función de puntero de un tipo de retorno de boost::variant<A, B, C, D, ...> lugar. 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;

A boost::variant es como una unión. Se sabe qué tipo se almacena en ella mirando qué objeto se utiliza para inicializar o asignar a la misma. Echar un vistazo a su href="http://www.boost.org/doc/libs/1_38_0/doc/html/variant.html" rel="noreferrer"> documentación . Por último, el uso de un puntero de función prima también es un poco más viejas. código C ++ Modern debe ser desacoplada de funciones / tipos específicos. Es posible que desee ver 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.

Otros consejos

No, no hay. Mi solución preferida a este problema es crear un diccionario que mapea el nombre de método de creación. Las clases que quieren ser creados como esto, entonces se registra un método de creación con el diccionario. Esto se discute en detalle en el GoF patrones libro .

La respuesta corta es que no puede. Ver estas preguntas para qué SO:

  1. ¿Por qué C ++ no tiene reflexión?
  2. ¿Cómo puedo añadir la reflexión para una aplicación C ++?

He contestado en otro SO pregunta sobre fábricas de C ++. Por favor, ver no si una fábrica flexible es de su interés. Intento describir una vieja manera de ET ++ para usar macros que ha funcionado muy bien para mí.

ET ++ era un proyecto para el puerto viejo MacApp a C ++ y X11. En el esfuerzo de ella Erich Gamma, etc. comenzó a pensar en Patrones de diseño

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 aunque es generar clases contenedoras que ocultan el mecanismo de mapeo y creación de objetos. El escenario común que encuentro es la necesidad de asignar diferentes clases derivadas de alguna clase base a las teclas, donde las clases derivadas todos tienen una firma constructora común disponible. Aquí está la solución que he encontrado hasta el momento.

#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

Estoy generalmente opuesto a la utilización macro pesado, pero he hecho una excepción aquí. El código anterior genera GENERIC_FACTORY_MAX_ARITY + versiones 1 de una clase denominada GenericFactory_N, para cada N entre 0 y GENERIC_FACTORY_MAX_ARITY inclusive.

Uso de las plantillas de clase generados es fácil. Suponga que desea una fábrica para crear BaseClass objetos usando un mapeo de cadena derivados. Cada uno de los objetos derivados tomar 3 números 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 clase GenericFactory_N es virtual para permitir que el 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 macro fábrica generador genérico

#define BOOST_PP_FILENAME_1 "GenericFactory.hpp"

Asume el archivo de cabecera de fábrica genérica se denomina GenericFactory.hpp

solución de Detalle para el registro de los objetos, y acceder a ellos con nombres de cadena.

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

compilar y ejecutar él (he hecho esto con Eclipse)

Salida:

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

Significado reflexión como en Java. hay alguna información aquí: http://msdn.microsoft.com/en-us /library/y0114hz2(VS.80).aspx

En términos generales, la búsqueda de Google para "reflexión c ++"

Tor Brede Vekterli proporciona una extensión impulso que da exactamente la funcionalidad que usted busca. Actualmente, es apropiado un poco torpe con libs a impulsar las actuales, pero yo era capaz de conseguir que funcione con 1.48_0 después de cambiar su espacio de nombres de 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 lleva la interfaz de usuario cadena de selección, y produce un objeto del tipo deseado.

La principal ventaja de utilizar el marco aquí (sobre el mantenimiento de una fruta-lista en algún lugar) es que la función de registro se encuentra en la definición de cada clase (y sólo requiere una sola línea de código que llama a la función de registro por clase registrada) - en lugar de un archivo que contiene la lista de fruta, que hay que añadir manualmente a cada vez que una nueva clase se deriva.

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

Este es el patrón de la fábrica. Ver Wikipedia (y este ejemplo). No se puede crear un tipo de por sí una cadena sin algún artilugio atroz. ¿Por qué necesita esto?

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top