反射(Reflection)是许多语言(如 C#,Java)都拥有的特性,用于在运行时获取类型信息,大大的提高了编程的灵活性,比如利用反射可以极大的简化 json/xml 解析、脚本绑定、属性编辑器等的实现。但是 C++ 并没有提供反射的支持,本文讨论在 C++ 中实现反射机制的一种方式。
反射主要特点有以下几点:
用map保存了字符串到动态类生成的函数指针的映射。
使用类名注册,根据不同的类名字符串构造成不同的类对象。
我的设计思路大致是这样的。
(1)为需要反射的类中定义一个创建该类对象的一个回调函数;
(2)设计一个工厂类,类中有一个std::map,用于保存类名和创建实例的回调函数。通过类工厂来动态创建类对象;
(3)程序开始运行时,将回调函数存入std::map(哈希表)里面,类名字做为map的key值;
第一步:定义一个函数指针类型,用于指向创建类实例的回调函数。
typedef void* (*CreateObject)(void)
第二步:定义和实现一个工厂类,用于保存保存类名和创建类实例的回调函数。工厂类的作用仅仅是用来保存类名与创建类实例的回调函数,所以程序的整个证明周期内无需多个工厂类的实例,所以这里采用单例模式来涉及工厂类。
//工厂类的定义
class ClassFactory{
private:
map<string, PTRCreateObject> m_classMap ;
ClassFactory(){}; //构造函数私有化
public:
void* getClassByName(string className);
void registClass(string name, PTRCreateObject method) ;
static ClassFactory& getInstance() ;
};
//工厂类的实现
//@brief:获取工厂类的单个实例对象
ClassFactory& ClassFactory::getInstance(){
static ClassFactory sLo_factory;
return sLo_factory ;
}
//@brief:通过类名称字符串获取类的实例
void* ClassFactory::getClassByName(string className){
map<string, PTRCreateObject>::const_iterator iter;
iter = m_classMap.find(className) ;
if ( iter == m_classMap.end() )
return NULL ;
else
return iter->second() ;
}
//@brief:将给定的类名称字符串和对应的创建类对象的函数保存到map中
void ClassFactory::registClass(string name, PTRCreateObject method){
m_classMap.insert(pair<string, PTRCreateObject>(name, method)) ;
}
第三步: 这一步比较重要,也是最值得深究的一步,也是容易犯迷糊的地方,仔细看。将定义的类注册到工厂类中。也就是说将类名称字符串和创建类实例的回调函数保存到工厂类的map中。这里我们又需要完成两个工作,第一个是定义一个创建类实例的回调函数,第二个就是将类名称字符串和我们定义的回调函数保存到工厂类的map中。假设我们定义了一个TestClassA。
//test class A
class TestClassA{
public:
void m_print(){
cout<<"hello TestClassA"<<endl;
};
};
//@brief:创建类实例的回调函数
TestClassA* createObjTestClassA{
return new TestClassA;
}
好了,我们完了第一个工作,定义了一个创建类实例的回调函数。下面我们要思考一下如何将这个回调函数和对应的类名称字符串保存到工厂类的map中。我这里的一个做法是创建一个全局变量,在创建这个全局变量时,调用的构造函数内将回调函数和对应的类名称字符串保存到工厂类的map中。在这里,这个全局变量的类型我们定义为RegisterAction。
//注册动作类
class RegisterAction{
public:
RegisterAction(string className,PTRCreateObject ptrCreateFn){
ClassFactory::getInstance().registClass(className,ptrCreateFn);
}
};
有个这个注册动作类,我们在每个类定义完成之后,我们就创建一个全局的注册动作类的对象,通过注册动作类的构造函数将我们定义的类的名称和回调函数注册到工厂类的map中。可以在程序的任何一个源文件中创建注册动作类的对象,但是在这里,我们放在回调函数后面创建。后面你就知道为什么这么做了。创建一个注册动作类的对象如下:
RegisterAction g_creatorRegisterTestClassA("TestClassA",(PTRCreateObject)createObjTestClassA);
到这里,我们就完成将类名称和创建类实例的回调函数注册到工厂类的map。下面再以另外一个类TestClassB为例,重温一下上面的步骤:
//test class B
class TestClassB{
public:
void m_print(){
cout<<"hello TestClassB"<<endl;
};
};
//@brief:创建类实例的回调函数
TestClassB* createObjTestClassB{
return new TestClassB;
}
//注册动作类的全局实例
RegisterAction g_creatorRegisterTestClassB("TestClassB",(PTRCreateObject)createObjTestClassB);
聪明的你,有没有发现,如果我们再定义一个类C、D….,我们重复的在写大量相似度极高的代码。那么我们如何偷懒呢,让代码变得简洁,提高我们的编码效率。有时我们就应该偷懒,不是说这个世界是懒人们创造的么,当然这些懒人们都很聪明。那么我们如何偷懒呢,如果你想到了宏,恭喜,答对了。其实仔细一看,包括回调函数的定义和注册动作的类的变量的定义,每个类的代码除了类名外其它都是一模一样的,那么我们就可以用下面的宏来替代这些重复代码。
#define REGISTER(className) \
className* objectCreator##className(){ \
return new className; \
} \
RegisterAction g_creatorRegister##className( \
#className,(PTRCreateObject)objectCreator##className)
有了上面的宏,我们就可以在每个类后面简单的写一个REGISTER(ClassName) 就完成了注册的功能,是不是很方便快捷呢!!!
至此,我们就完成了C++反射的部分功能,为什么是部分功能,后面再另外说明。急不可耐,我们先来测试一下,是否解决了上面我们提到的问题:如何通过类的名称字符串来生成类的对象。测试代码如下:
#include
#include
#include
using namespace std;
//test class
class TestClass{
public:
void m_print(){
cout<<"hello TestClass"<<endl;
};
};
REGISTER(TestClass);
int main(int argc,char* argv[]){
TestClass* ptrObj=(TestClass*)ClassFactory::getInstance().getClassByName("TestClass");
ptrObj->m_print();
}