前几天用C++为《捕鱼达人》移植UI编辑器的时候,遇到了几个难点。一个是通过类名的字符串创建相应的类的实例化。还有一个是通过属性的名字字符串来操作相应的类的属性。用支持反射的Objective-C或者Java语言来实现类似功能是非常简单的。但是C++不支持,纠结了几天,终于实现了类似于反射的功能。
思路分为以下几步:
1、在要反射的类中定义一个回调函数,用来创建这个类的实例;
2、设计一个工厂类,类中有一个std::map,用于保存类名和创建实例的回调函数。通过类工厂来动态创建类对象;
3、程序开始运行时,将回调函数存入std::map(哈希表)里面,类名字做为map的key值;
下面我来一步一步的讲解具体的实现方法。
首先声明一个回调函数
typedef void* (*createClass)(void) ;
定义一个工厂类
class CKClassFactory
{
public:
CKClassFactory() ;
virtual ~CKClassFactory() ;
void* getClassByName(string className) ;
void registClass(string name, createClass method) ;
static CKClassFactory& sharedClassFactory() ;
private:
map<string, createClass> m_classMap ;
} ;
m_classMap用来保存回调函数指针,通过registClass()函数来实现类名和函数的插入。
getClassByName()函数返回map中通过回调函数创建的类的实例化,参数为传入的类名。
下面是实现方法。
void* CKClassFactory::getClassByName(string className)
{
map<string, createClass>::const_iterator iter ;
iter = m_classMap.find(className) ;
if ( iter == m_classMap.end() )
return NULL ;
else
return iter->second() ;
}
void CKClassFactory::registClass(string name, createClass method)
{
m_classMap.insert(pair<string, createClass>(name, method)) ;
}
CKClassFactory& CKClassFactory::sharedClassFactory()
{
static CKClassFactory _sharedClassFactory ;
return _sharedClassFactory ;
}
这样,我们的单件工厂类就设计完成了。
下面我们需要设计一个用来动态创建类的类,被创建的类通过本类的一个静态对象来向类工厂注册对象创建的函数。
class CKDynamicClass
{
public:
CKDynamicClass(string name, createClass method)
{
CKClassFactory::sharedClassFactory().registClass(name, method) ;
}
} ;
#define DECLARE_CLASS(className)\
string className##Name ; \
static CKDynamicClass* m_className##dc ;
#define IMPLEMENT_CLASS(className) \
CKDynamicClass* className::m_className##dc = \
new CKDynamicClass(#className, className::createInstance) ;
该类设计了两个宏,用于实现动态创建,后面的类中会用到。
下面来设计一个基类,所有动态创建的类是继承于该类的。
该类主要为了实现两个功能,一个是配合类工厂来创建一个新的子类,一个是通过map来动态创建类的属性,动态创建属性类似于上面的动态创建类。
下面先帖代码:
typedef void (*setValue)(CKBaseClass *t, void* c) ;
class CKBaseClass
{
private:
DECLARE_CLASS(CKBaseClass)
public:
CKBaseClass() {}
virtual ~CKBaseClass() {}
static void* createInstance() {return new CKBaseClass();}
virtual void registProperty() {}
virtual void display() {}
map<string, setValue> m_propertyMap ;
} ;
#define SYNTHESIZE(classType, varType, varName) \
public: \
inline static void set##varName(CKBaseClass*cp, void*value){ \
classType* tp = (classType*)cp ; \
tp->varName = (varType)value ; \
} \
inline varType get##varName(void) const { \
return varName ; \
}
IMPLEMENT_CLASS(CKBaseClass)
先有一个函数指针,用来动态创建属性并赋值。函数指针插入到m_propertyMap里面,key值为函数的名字。
createInstance函数返回了本类的实例化,通过DECLARE_CLASS宏,声明了一个字符串和一个静态的CKDynamicClass类指针,类的外面通过IMPLEMENT_CLASS宏对CKDynamicClass类指针进行初始化,这里通过在构造传入的类名和createInstance函数),来将对应的函数插入到类工厂的map里面。
到这一步,类反射的功能即将要实现了,喝口水,歇下接着写。
类里面还有两个东东没有介绍,registProperty函数,用来注册创建属性的函数指针,类似于创建类,将对应的函数插入到map里面。display函数用来后面做测试。
下面我们来设计最后一个类,就是将要被动态创建的类,先帖码。
public:
SYNTHESIZE(CKHelloClass, int*, m_pValue)
CKHelloClass() {}
virtual ~CKHelloClass(){}
static void* createInstance()
{
return new CKHelloClass() ;
}
virtual void registProperty()
{
m_propertyMap.inset(pair<string, setValue>("setm_pValue", setm_pValue)) ;
}
virtual void display()
{
cout << *getm_pValue() << endl ;
}
protected:
int *m_pValue ;
} ;
IMPLEMENT_CLASS(CKHelloClass)
CKHelloClass类是我们要动态创建出来的类,继承于CKBaseClass。
DECLARE_CLASS的宏是必须要实现的,声明了一个字符串和一个静态的CKDynamicClass类指针,类的外面通过IMPLEMENT_CLASS宏对CKDynamicClass类指针进行初始化。
SYNTHESIZE宏,用来给m_pValue来实现Set和Get方法。
createInstance函数返回本类的实例化。
registProperty函数来将Set函数插入到map中。
display函数用来测试m_pValue指针是否赋值成功。
最后一步,在main函数中来实现。
CKBaseClass *pVar = (CKBaseClass*)CKClassFactory::sharedClassFactory().getClassByName("CKHelloClass") ;
pVar->registProperty() ;
int pValue = 123456 ;
pVar->m_propertyMap["setm_pValue"](pVar, &pValue) ;
pVar->display() ;
声明一个基类的指针,通过CKClassFactory的getClassByName来返回一个CKHelloClass的实例化赋值给该指针pVar。
pVar调用registProperty函数来注册属性,属性的Set函数被插入到CKHelloClass的m_propertyMap里面。
我们来做下测试,声明一个变量pValue并赋值,然后传入m_proeprtyMap里面,key值为CKHelloClass中的Set函数。这样,Set函数会被相应调用,然后将pValue的指针赋值给相应的CKHelloClass成员指针m_pValue,同过display函数调用,成功的打印出m_pValue指针的值。
到此,C++反射的实现讲解完成,这类实现方法适合在开发各类的编辑器中去使用,包括微软的MFC等等。在开发编辑器的阶段,并没有相应的类,但是使用者想通过在编辑器中传入类的名字,然后在开发中,根据编辑器传入的类名,新建一个类去实现的话,这种方法非常适用,更多的用法等待大家去发掘。
最后放上本文章的源代码,XCode工程,使用GCC4.2编译器。代码中用到了标准C++库STL,稍微修改工程,即可实现多平台编译。
C++反射源代码
谢谢大家。