¿Hay alguna manera de crear instancias de objetos a partir de una cadena que contenga su nombre de clase?

Resuelto Gal Goldman asked hace 15 años • 12 respuestas

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 alguna manera de convertir de alguna manera esta cadena a un tipo (clase) real, de modo que BaseFactory no tenga que conocer todas las clases derivadas posibles y tener if() para cada una de ellas? ¿Puedo producir una clase a partir de esta cadena?

Creo que esto se puede hacer en C# a través de Reflection. ¿Existe algo similar en C++?

Gal Goldman avatar Feb 24 '09 22:02 Gal Goldman
Aceptado

No, no hay ninguno, a menos que hagas el mapeo tú mismo. C++ no tiene ningún mecanismo para crear objetos cuyos tipos se determinen en tiempo de ejecución. Sin embargo, puedes usar un mapa para hacer ese mapeo tú mismo:

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 luego puedes hacer

return map[some_string]();

Obteniendo una nueva instancia. Otra idea es que los tipos se registren solos:

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

Podrías decidir crear una macro para el registro.

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

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

Aunque estoy seguro de que hay mejores nombres para esos dos. 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 función un tipo de retorno de boost::variant<A, B, C, D, ...>. Si tienes una clase Foo, Bar y Baz, se ve así:

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::variantes como una unión. Sabe qué tipo está almacenado en él al observar qué objeto se usó para inicializarlo o asignarle. Echa un vistazo a su documentación aquí . Finalmente, el uso de un puntero de función sin formato también es un poco anticuado. El código C++ moderno debe estar desacoplado de funciones/tipos específicos. Es posible que desee investigar Boost.Functionpara buscar una mejor manera. Entonces quedaría así (el mapa):

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

std::functionTambién estará disponible en la próxima versión de C++, incluido std::shared_ptr.

Johannes Schaub - litb avatar Feb 24 '2009 16:02 Johannes Schaub - litb

No, no lo hay. Mi solución preferida a este problema es crear un diccionario que asigne el nombre al método de creación. Las clases que quieran crearse de esta manera registran un método de creación con el diccionario. Esto se analiza con cierto detalle en el libro de patrones GoF .

 avatar Feb 24 '2009 16:02

La respuesta corta es que no puedes. Consulte estas preguntas SO para saber por qué:

  1. ¿Por qué C++ no tiene reflexión?
  2. ¿Cómo puedo agregar reflexión a una aplicación C++?
Michael Kristofik avatar Feb 24 '2009 16:02 Michael Kristofik