java和c#等高级语言拥有反射机制,可以通过字符串很方便的生成对应实体。这极大的方便了对象的序列化及对象的生产效率。C++开发人员一直想要这么一个机制,希望仅通过配置文件就可以改变程序的行为却不需要修改代码。现实是,我们将希望寄托在工厂类上,需要修改行为,则需要修改工厂的生产产品。尽管将修改集中于一处或者几处,但仍旧是修改了代码,不够完美。所以C++能不能拥有反射机制呢?答案是有的,虽然语言本身不支持,但是我们可以自食其力。
反射说白了,是通过字符串去产生对应的类对象,也就是说字符串和产生类的方法之间需要有一个映射关系。我们在C++中暂且由std::map来实现。键值可以用类名转换成的字符串来担当,除此之外还需要一个产生对象的函数。这个函数应该具有通用性,因为所有的具有反射功能的类都需要插入键值对到定义好的std::map中。
我们可以暂定这个函数具有如下形式:
typedef void * (*CreateObj)();
这个函数返回一个void*的指针,可以承载所有的类。另外,CreateObj是不带参数的,这是我们为实现动态生成对象必须要遵循的约束,那就是所有需要进行反射的类需要提供默认构造函数,否则无法初始化。
另外,为了生成对象,CreateObj必须知道每个类的存在,这并不合理。实际上来说,每个CreateObj只要知道自己所要生成的对象即可,所以我们完全可以在每个类当中定义这么一个静态函数,用于类对象的生成。
我们的数据结构已经出来了,那就是:
std::map<std::string, CreateObj> m_mapCreator;
紧接着,我们需要一个对象来管理这个数据结构,我们期望这个类可以管理新的反射关系的添加,可以为我们提供一个通过类名字符串就返回类对象的函数:
class CRefect
{
public:
typedef void* (*CreateObject)();
static CRefect* instance()
{
static CRefect cr;
return &cr;
}
template<class T>
T* Create(std::string t)
{
return (T*)(m_mapCreat[t]());
}
void* registerProperty(std::string key, CreateObject pC)
{
m_mapCreat[key] = pC;
return NULL;
}
private:
std::map<std::string, CreateObject> m_mapCreat;
};
上面的类定义简洁明了,我们设计成单例,保证了映射表的唯一性。另外提供了注册键值对的函数,提供了动态生成对象的函数。看到上面定义,有人会问为什么registerProperty会有void*的返回值,意义何在?
这个问题可以和另外一个问题一起回答:
何时对需要反射的类进行注册?
我们最希望在程序运行到main函数之前就对所有的类进行注册。C++恰好有这么一种内置机制,那就是static变量的初始化,是先于我们的应用逻辑的。
好,解决了上面的所有疑问,我们设定以下两个宏:
// 声明classname具有反射能力
#define DECLARE_DYNAMIC(classname)\
static void* CreateObject()\
{\
return new classname;\
}\
static void* s_null; // 为每个类设定一个静态指针,编译器要求我们对其进行初始化。
初始化的时候正是我们注册映射关系的好时机。但是我们并不需要s_null有真正的意义,
所以,将registerProperty的返回值设定为void*,并返回为null
// 对classname的反射能力进行实现,注册映射关系
#define IMPL_DYNAMIC(classname)\
void* classname::s_null = CRefect::instance()->registerProperty(#classname, &classname::CreateObject);
最后我们看看怎么来使我们的自定义类拥有反射能力呢?
class CTest
{
public:
void print()
{
cout<<"print"<<endl;
}
DECLARE_DYNAMIC(CTest);
};
IMPL_DYNAMIC(CTest);
测试一下:
int _tmain(int argc, _TCHAR* argv[])
{
CTest* gt = CRefect::instance()->Create<CTest>("CTest");
gt->print();
getchar();
AA a;
delete gt;
return 0;
}
至此,一个简单的反射框架就写好了。可以看到,使用也是非常方便。
其实为了使用方便,我们甚至可以更进一步:
#define DYNAMIC_CREATE(classname) \
CRefect::instance()->Create<classname>(#classname);
CTest* gt = DYNAMIC_CREAE(CTest);使用将更加简洁。
能力有限,难免有疏忽之处。欢迎广大程序员朋友提出宝贵意见。