C++ 有没有办法从包含类名的字符串中实例化对象?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/582331/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is there a way to instantiate objects from a string holding their class name?
提问by Gal Goldman
I have a file: Base.h
我有一个文件:Base.h
class Base;
class DerivedA : public Base;
class DerivedB : public Base;
/*etc...*/
and another file: BaseFactory.h
和另一个文件: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.*/
Is there a way to somehow convert this string to an actual type (class), so that BaseFactory wouldn't have to know all the possible Derived classes, and have if() for each one of them? Can I produce a class from this string?
有没有办法以某种方式将此字符串转换为实际类型(类),这样 BaseFactory 就不必知道所有可能的派生类,并且对每个类都有 if() ?我可以从这个字符串生成一个类吗?
I think this can be done in C# through Reflection. Is there something similar in C++?
我认为这可以通过反射在 C# 中完成。C ++中有类似的东西吗?
回答by Johannes Schaub - litb
Nope, there is none, unless you do the mapping yourself. C++ has no mechanism to create objects whose types are determined at runtime. You can use a map to do that mapping yourself, though:
不,没有,除非你自己做映射。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>;
And then you can do
然后你可以做
return map[some_string]();
Getting a new instance. Another idea is to have the types register themself:
获取新实例。另一个想法是让类型自行注册:
// 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");
You could decide to create a macro for the registration
您可以决定为注册创建一个宏
#define REGISTER_DEC_TYPE(NAME) \
static DerivedRegister<NAME> reg
#define REGISTER_DEF_TYPE(NAME) \
DerivedRegister<NAME> NAME::reg(#NAME)
I'm sure there are better names for those two though. Another thing which probably makes sense to use here is shared_ptr
.
我相信这两个名字有更好的名字。在这里使用可能有意义的另一件事是shared_ptr
.
If you have a set of unrelated types that have no common base-class, you can give the function pointer a return type of boost::variant<A, B, C, D, ...>
instead. Like if you have a class Foo, Bar and Baz, it looks like this:
如果您有一组没有共同基类的不相关类型,您可以给函数指针一个返回类型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
is like an union. It knows which type is stored in it by looking what object was used for initializing or assigning to it. Have a look at its documentation here. Finally, the use of a raw function pointer is also a bit oldish. Modern C++ code should be decoupled from specific functions / types. You may want to look into Boost.Function
to look for a better way. It would look like this then (the map):
Aboost::variant
就像一个工会。它通过查看用于初始化或分配给它的对象来知道其中存储的是哪种类型。在此处查看其文档。最后,使用原始函数指针也有点陈旧。现代 C++ 代码应该与特定的函数/类型分离。您可能需要调查Boost.Function
以寻找更好的方法。它看起来像这样(地图):
typedef std::map<std::string, boost::function<variant_type()> > map_type;
std::function
will be available in the next version of C++ too, including std::shared_ptr
.
std::function
也将在下一版本的 C++ 中可用,包括std::shared_ptr
.
回答by Gal Goldman
No there isn't. My preferred solution to this problem is to create a dictionary which maps name to creation method. Classes that want to be created like this then register a creation method with the dictionary. This is discussed in some detail in the GoF patterns book.
不,没有。我对这个问题的首选解决方案是创建一个字典,将名称映射到创建方法。想要像这样创建的类然后在字典中注册一个创建方法。这在GoF 模式书中有详细讨论。
回答by Michael Kristofik
The short answer is you can't. See these SO questions for why:
简短的回答是你不能。请参阅这些 SO 问题以了解原因:
回答by epatel
I have answered in another SO question about C++ factories. Please see thereif a flexible factory is of interest. I try to describe an old way from ET++ to use macros which has worked great for me.
我已经在另一个关于 C++ 工厂的 SO 问题中回答了。如果您对灵活的工厂感兴趣,请查看那里。我试图描述一种从 ET++ 到使用宏的旧方法,它对我很有用。
ET++was a project to port old MacApp to C++ and X11. In the effort of it Eric Gamma etc started to think about Design Patterns
ET++是一个将旧 MacApp 移植到 C++ 和 X11 的项目。在它的努力下,Eric Gamma 等开始考虑设计模式
回答by texta83
boost::functional has a factory template which is quite flexible: http://www.boost.org/doc/libs/1_54_0/libs/functional/factory/doc/html/index.html
boost::functional 有一个非常灵活的工厂模板:http: //www.boost.org/doc/libs/1_54_0/libs/functional/factory/doc/html/index.html
My preference though is to generate wrapper classes which hide the mapping and object creation mechanism. The common scenario I encounter is the need to map different derived classes of some base class to keys, where the derived classes all have a common constructor signature available. Here is the solution I've come up with so far.
不过,我的偏好是生成隐藏映射和对象创建机制的包装类。我遇到的常见场景是需要将某些基类的不同派生类映射到键,其中派生类都有一个可用的公共构造函数签名。这是我到目前为止提出的解决方案。
#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
I am generally opposed to heavy macro use, but I've made an exception here. The above code generates GENERIC_FACTORY_MAX_ARITY + 1 versions of a class named GenericFactory_N, for each N between 0 and GENERIC_FACTORY_MAX_ARITY inclusive.
我通常反对大量使用宏,但我在这里做了一个例外。上面的代码为 0 和 GENERIC_FACTORY_MAX_ARITY 之间的每个 N 生成名为 GenericFactory_N 的类的 GENERIC_FACTORY_MAX_ARITY + 1 个版本。
Using the generated class templates is easy. Suppose you want a factory to create BaseClass derived objects using a string mapping. Each of the derived objects take 3 integers as constructor parameters.
使用生成的类模板很容易。假设您希望工厂使用字符串映射创建 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);
The GenericFactory_N class destructor is virtual to allow the following.
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;
Note that this line of the generic factory generator macro
请注意,通用工厂生成器宏的这一行
#define BOOST_PP_FILENAME_1 "GenericFactory.hpp"
Assumes the generic factory header file is named GenericFactory.hpp
假设通用工厂头文件名为 GenericFactory.hpp
回答by user3458845
Detail solution for registering the objects, and accessing them with string names.
注册对象并使用字符串名称访问它们的详细解决方案。
common.h
:
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
:
/*
* 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
:
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)
编译并运行它(已使用 Eclipse 完成此操作)
Output:
输出:
Base constructor
test1 constructor m_a=10m_b=20
Base constructor
test1 constructor m_a=30m_b=40
回答by DAmann
Tor Brede Vekterli provides a boost extension that gives exactly the functionality you seek. Currently, it is slightly awkward fitting with current boost libs, but I was able to get it working with 1.48_0 after changing its base namespace.
Tor Brede Vekterli 提供了一个 boost 扩展,可以提供您所寻求的功能。目前,与当前的 boost 库配合有点尴尬,但在更改其基本命名空间后,我能够使其与 1.48_0 一起使用。
In answer to those who question why such a thing (as reflection) would be useful for c++ - I use it for interactions between the UI and an engine - the user selects an option in the UI, and the engine takes the UI selection string, and produces an object of the desired type.
回答那些质疑为什么这样的东西(作为反射)对 c++ 有用的人 - 我将它用于 UI 和引擎之间的交互 - 用户在 UI 中选择一个选项,引擎采用 UI 选择字符串,并生成所需类型的对象。
The chief benefit of using the framework here (over maintaining a fruit-list somewhere) is that the registering function is in each class's definition (and only requires one line of code calling the registration function per registered class) - as opposed to a file containing the fruit-list, which must be manually added to each time a new class is derived.
在这里使用框架的主要好处(而不是在某处维护水果列表)是注册函数在每个类的定义中(并且只需要一行代码来调用每个注册类的注册函数)——而不是包含一个文件水果列表,每次派生新类时都必须手动添加。
I made the factory a static member of my base class.
我使工厂成为我基类的静态成员。
回答by Ido Weinstein
Meaning reflection as in Java. there is some info here: http://msdn.microsoft.com/en-us/library/y0114hz2(VS.80).aspx
Java 中的含义反射。这里有一些信息:http: //msdn.microsoft.com/en-us/library/y0114hz2(VS.80).aspx
Generally speaking, search google for "c++ reflection"
一般来说,谷歌搜索“c++反射”