有没有办法从保存类名的字符串实例化对象?

我有个文件 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 + + 中有类似的东西吗?

94915 次浏览

Java 中的意思反射。 这里有一些信息: Http://msdn.microsoft.com/en-us/library/y0114hz2(vs.80).aspx

一般来说,搜索“ c + + 反射”

不,没有。我对这个问题的首选解决方案是创建一个字典,它将名称映射到创建方法。想要像这样创建的类然后在字典中注册一个创建方法。这在 GoF 模式书中有详细讨论。

这是工厂模式。参见维基百科(以及 这个示例)。如果没有一些令人震惊的技巧,就不可能从字符串本身创建类型。你要这个干什么?

我在另一个关于 C + + 工厂的 SO 问题中回答了这个问题。请参阅 那里,如果一个灵活的工厂是感兴趣的。我试图描述一种从 ET + + 到使用宏的老方法,这种方法对我来说非常有效。

ET + + 是一个将旧的 MacApp 移植到 C + + 和 X11的项目。在努力的过程中,Eric Gamma 等人开始思考 < em > 设计模式

简短的回答是你不能,看看这些问题为什么:

  1. 为什么 C + + 没有反射?
  2. 如何向 C + + 应用程序添加反射?

不,没有,除非你自己绘制地图。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

Tor Brede Vekterli 提供了一个提升扩展,可以准确地提供您所需要的功能。目前,它与当前的升级库有些不协调,但是在更改了基本名称空间之后,我能够使它与1.48 _ 0一起工作。

Http://arcticinteractive.com/static/boost/libs/factory/doc/html/factory/factory.html#factory.factory.reference

为了回答那些质疑这样一个东西(作为反射)为什么对 c + + 有用的人——我使用它来实现 UI 和引擎之间的交互——用户在 UI 中选择一个选项,引擎获取 UI 选择字符串,并生成所需类型的对象。

在这里使用框架的主要好处(比在某个地方维护一个水果列表)是注册函数在每个类的定义中(并且只需要一行代码调用每个注册类的注册函数)——而不是包含水果列表的文件,它必须在每次派生一个新类时手动添加。

我将工厂作为基类的静态成员。

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

我通常反对大量使用宏,但这里例外。上面的代码生成一个名为 GenericFactory _ N 的类的 GENERIC _ FACTORY _ MAX _ ARITY + 1个版本,对于0到 GENERIC _ FACTORY _ MAX _ ARITY 之间的每个 N。

使用生成的类模板很容易。假设您希望工厂使用字符串映射创建 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

注册对象并使用字符串名称访问它们的详细解决方案。

返回文章页面

#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
*
*  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_ */

返回文章页面

#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 中完成了这项工作)

产出:

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

是的,不使用框架和宏,仅仅获取类方法和构造函数的内存地址是可能的。当为此操作配置时,您可以从链接器生成的映射中检索它们。

访问这个网站

Https://ealaframework.no-ip.org/wiki/page/c.reference

一个 C + + 11风格的完整例子:

// Base.h
class Base;
class DerivedA : public Base;
class DerivedB : public Base;


// BaseFactory.h
class BaseFactory
{
public:
static BaseFactory& get() {
static BaseFactory singleton;
return singleton;
}


virtual ~BaseFactory() {};


BaseFactory(const BaseFactory&) = delete;
BaseFactory(BaseFactory&&) = delete;


template <class DerivedClass>
static std::shared_ptr<Base> creator()
{
return std::shared_ptr<Base>(new DerivedClass());
}


template <class DerivedClass>
void register_class(const std::string& class_name)
{
if (name_to_creator_map.find(class_name) == name_to_creator_map.end())
{
std::function<std::shared_ptr<Base>(void)> functor = &BaseFactory::template creator<DerivedClass>;
name_to_creator_map.emplace(class_name, functor);
}
}


std::shared_ptr<Base> create(const std::string& class_name) const;


private:
BaseFactory();


std::map<std::string, std::function<std::shared_ptr<Base>(void)>> name_to_creator_map;
};


// example.cpp using BaseFactory
BaseFactory::get().register_class<DerivedA>("DerivedA");
BaseFactory::get().register_class<DerivedB>("DerivedB");
auto a_obj = BaseFactory::get().create("DerivedA");
auto b_obj = BaseFactory::get().create("DerivedB");

有一个可以随时使用的反射库 https://www.rttr.org/。你可以很容易地通过字符串实例化类。

struct MyStruct { MyStruct() {}; void func(double) {}; int data; };


RTTR_REGISTRATION
{
registration::class_<MyStruct>("MyStruct")
.constructor<>()
.property("data", &MyStruct::data)
.method("func", &MyStruct::func);
}




type t = type::get_by_name("MyStruct");
variant var = t.create();