У меня есть файл: 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 # через Reflection. Есть ли что-то подобное в C ++?
c++
inheritance
factory
instantiation
Гал Гольдман
источник
источник
Ответы:
Нет, нет, если вы сами не составите карту. В 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;
А
boost::variant
похоже на союз. Он знает, какой тип хранится в нем, глядя, какой объект использовался для его инициализации или присвоения ему. Взгляните на его документацию здесь . Наконец, использование необработанного указателя на функцию также немного устарело. Современный код C ++ должен быть отделен от определенных функций / типов. Возможно, вы захотитеBoost.Function
найти лучший способ. Тогда это выглядело бы так (карта):typedef std::map<std::string, boost::function<variant_type()> > map_type;
std::function
будет доступен и в следующей версии C ++, в том числеstd::shared_ptr
.источник
DerivedB::reg
он действительно инициализирован? Насколько я понимаю, он может вообще не быть построен, если в модуле перевода не определена функция или объект, как указано вderivedb.cpp
3.6.2.BaseFactory::map_type * BaseFactory::map = NULL;
в моем файле cpp. Без этого компоновщик жаловался на неизвестную карту символов.DerivedB::reg
не инициализируется, если ни одна из его функций или экземпляров не определена в блоке переводаderivedb.cpp
. Это означает, что класс не регистрируется до тех пор, пока не будет активирован. Кто-нибудь знает обходной путь для этого?Нет, нет. Мое предпочтительное решение этой проблемы - создать словарь, который сопоставляет имя с методом создания. Классы, которые хотят быть созданы таким образом, затем регистрируют метод создания в словаре. Это подробно обсуждается в книге шаблонов GoF .
источник
Короткий ответ: вы не можете. Посмотрите эти SO-вопросы, чтобы узнать, почему:
источник
Я ответил еще на один вопрос SO о фабриках C ++. Пожалуйста, посмотрите там, если вас интересует гибкая фабрика. Я пытаюсь описать старый способ использования макросов из ET ++, который мне очень понравился.
ET ++ был проектом по переносу старого MacApp на C ++ и X11. Эрик Гамма и другие начали думать о паттернах дизайна.
источник
boost :: function имеет довольно гибкий фабричный шаблон: 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;
Обратите внимание, что эта строка макроса универсального генератора фабрики
#define BOOST_PP_FILENAME_1 "GenericFactory.hpp"
Предполагается, что общий заголовочный файл фабрики называется GenericFactory.hpp.
источник
Детальное решение для регистрации объектов и доступа к ним по строковым именам.
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; }
Скомпилируйте и запустите (сделали это с помощью Eclipse)
Выход:
источник
Имеется в виду отражение как в Java. Здесь есть информация: http://msdn.microsoft.com/en-us/library/y0114hz2(VS.80).aspx
В общем, ищите в Google "отражение c ++"
источник
Tor Brede Vekterli предоставляет расширение для ускорения, которое дает именно ту функциональность, которую вы ищете. В настоящее время он немного неудобно подходит для текущих библиотек boost, но мне удалось заставить его работать с 1.48_0 после изменения его базового пространства имен.
http://arcticinteractive.com/static/boost/libs/factory/doc/html/factory/factory.html#factory.factory.reference
Отвечая тем, кто задается вопросом, почему такая вещь (как отражение) может быть полезна для С ++ - я использую ее для взаимодействия между пользовательским интерфейсом и движком - пользователь выбирает параметр в пользовательском интерфейсе, а движок принимает строку выбора пользовательского интерфейса, и создает объект желаемого типа.
Основное преимущество использования фреймворка здесь (по сравнению с сохранением где-то списка фруктов) заключается в том, что функция регистрации присутствует в определении каждого класса (и требует только одной строки кода, вызывающей функцию регистрации для каждого зарегистрированного класса) - в отличие от файла, содержащего список фруктов, который необходимо добавлять вручную каждый раз, когда создается новый класс.
Я сделал фабрику статическим членом моего базового класса.
источник
Это заводской узор. См. Википедию (и этот пример). Вы не можете создать тип как таковой из строки без какого-либо вопиющего взлома. зачем вам это?
источник