Есть ли способ создать экземпляр объектов из строки, содержащей их имя класса?
у меня есть файл: Base.h
class Base;
class DerivedA : public Base;
class DerivedB : public Base;
/*etc...*/
и еще один файл: 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.*/
есть ли способ каким-то образом преобразовать эту строку в фактический тип (класс), так что BaseFactory не должен был бы знать все возможные производные классы и иметь if() для каждого из них? Могу ли я создать класс из этой строки?
Я думаю, что это можно сделать в C# через отражение. Есть ли что-то подобное в C++?
9 ответов:
нет, нет, если вы не сделаете отображение себя. В C++ нет механизма для создания объектов, типы которых определяются во время выполнения. Вы можете использовать карту, чтобы сделать это отображение самостоятельно, хотя:
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>;
и тогда вы можете сделать
return map[some_string]();
получение нового экземпляра. Другая идея состоит в том, чтобы типы регистрировались сами:
// 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");
вы можете решить создать макрос для регистрации
#define REGISTER_DEC_TYPE(NAME) \ static DerivedRegister<NAME> reg #define REGISTER_DEF_TYPE(NAME) \ DerivedRegister<NAME> NAME::reg(#NAME)
я уверен, что есть лучше хотя имена для этих двоих есть. Еще одна вещь, которая, вероятно, имеет смысл использовать здесь
shared_ptr
.если у вас есть набор несвязанных типов, которые не имеют общего базового класса, вы можете дать указатель на функцию тип возвращаемого
boost::variant<A, B, C, D, ...>
вместо. Например, если у вас есть класс Foo, Bar и Baz, это выглядит так: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
как союз. Он знает, какой тип хранится в нем, глядя, какой объект был использован для инициализации или назначения ему. Взгляните на его документация здесь. Наконец, использование необработанного указателя функции также немного устарело. Современный C++ код должен быть отделен от конкретных функций / типов. Вы можете посмотреть вBoost.Function
искать лучший способ. Тогда это выглядело бы так (карта):typedef std::map<std::string, boost::function<variant_type()> > map_type;
std::function
будет доступен в следующей версии C++ тоже, в том числеstd::shared_ptr
.
нет. Мой предпочтительным решением этой проблемы является создать словарь, который сопоставляет имя метода создания. Классы, которые хотят быть созданы таким образом, затем зарегистрировать метод создания со словарем. Это обсуждается более подробно в GoF patterns book.
Я ответил на другой вопрос SO о фабриках C++. Пожалуйста, смотрите здесь Если гибкая фабрика представляет интерес. Я пытаюсь описать старый способ из ET++ использовать макросы, который отлично работал для меня.
ET++ был проект для переноса старого MacApp на C++ и X11. В попытке этого Эрик гамма и т. д. начал думать о Шаблоны Проектирования
boost:: functional имеет заводской шаблон, который является довольно гибким: http://www.boost.org/doc/libs/1_54_0/libs/functional/factory/doc/html/index.html
однако мое предпочтение заключается в создании классов-оболочек, которые скрывают механизм отображения и создания объектов. Общий сценарий, с которым я сталкиваюсь, - это необходимость сопоставления различных производных классов некоторого базового класса с ключами, где все производные классы имеют общую подпись конструктора. Вот решение проблемы Я уже придумал все это.
#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
Я вообще против использования тяжелых макросов, но я сделал исключение здесь. Приведенный выше код генерирует GENERIC_FACTORY_MAX_ARITY + 1 версии класса с именем GenericFactory_N, для каждого N между 0 и GENERIC_FACTORY_MAX_ARITY включительно.
использование сгенерированных шаблонов классов легко. Предположим, вы хотите, чтобы фабрика создавала производные объекты BaseClass с помощью строкового сопоставления. Каждый из производных объектов принимает 3 целых числа в качестве конструктора параметры.
#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);
деструктор класса GenericFactory_N является виртуальным, чтобы разрешить следующее.
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;
обратите внимание, что эта строка макроса generic factory generator
#define BOOST_PP_FILENAME_1 "GenericFactory.hpp"
предполагается, что общий файл заголовка фабрики называется GenericFactory.ГЭС
означает отражение, как в Java. здесь есть некоторая информация: http://msdn.microsoft.com/en-us/library/y0114hz2(против.80).аспн
вообще говоря, поиск Google для "C++ отражение"
--------------- 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
это заводская модель. Смотрите Википедию (и этой примеру). Вы не можете создать тип как таковой из строки без какого-то вопиющего взлома. Зачем тебе это нужно?
Tor Brede Vekterli предоставляет расширение boost, которое дает именно ту функциональность, которую вы ищете. В настоящее время это немного неудобно подходит для текущих библиотек boost, но я смог заставить его работать с 1.48_0 после изменения его базового пространства имен.
в ответ на вопрос, почему такая вещь (как отражение) была бы полезна для c++ - я использую его для взаимодействия между пользовательским интерфейсом и движком - пользователь выбирает параметр в пользовательском интерфейсе, а движок принимает строку выбора пользовательского интерфейса и создает объект нужного типа.
главное преимущество использования фреймворка здесь (над поддержанием списка фруктов где-то) заключается в том, что функция регистрации находится в определении каждого класса (и требует только одной строки кода, вызывающей функцию регистрации для каждого зарегистрированного класса) - в отличие от файла, содержащего fruit-список, который необходимо вручную добавлять к каждому новому производному классу.
Я сделал фабрику статическим членом моего базового класса.