第三回 运行时类信息(Runtime Class Information)
刚开始学习MFC的时候,就碰到有关于RunTime Class Info的东西,可以在运行时得知一个对象的指针到底是什么类型的.当时并没有深入研究,用得也不多,只是觉得挺神奇的.而在这个engine的开发过程 中,才渐渐觉得这是个好东西了.我在engine里实现了一套这样的系统,用着还蛮顺手的,在这里介绍一下.
基本上这套系统(Runtime Class Information System,暂时简称为RtCIS吧)的功能有:
*.最重要的,就是可以为使用 RtCIS的类提供一个统一的内存分配器,所有这个类的实例全部从这个池中分配,而因为这个内存分配器是针对单一类型的数据的,所以可以写的比较简单而有 效率(使用模板),并且,可以用类的名字来标识这个内存分配池,这样可以方便内存池输出一些调试信息.
*.由于这个类的实例是分配在一个统一的内存池里的,所以可以方便快速的枚举出这个类的所有实例,这在有时候是很有用的.
*.可以根据类名(一个字符串)在运行时创建一个类的对象
*.可以在运行时来判断一个对象属于什么类型
这套系统的主要是通过在一个类中添加静态变量方法来实现的,下面列出一套精简版的实现,engine中的实现会复杂一些,包括一些其它的功能,比如记录类 的继承信息,枚举类的实例,为每个类分配一个ID,根据这个ID来创建实例等,这些功能全部可以在以下代码的基础上扩充,就不列举了.
*.首先定义一个类信息的基类:CClass
class CClass
{
public:
virtual void *New()=0;
virtual void Delete(void *)=0;
const char*GetName() { return _classname.c_str(); }
static void *NewByName(const char*clssname)
{
return _classes[std::string(clssname)]->New();
}
protected:
void SetName(const char *name)
{
_classname=name;
_classes[_classname]=this;//将自己注册到全局的类信息表格中去
}
std::string _classname;//类的名字
static std::map<std::string,CClass*> _classes;//全局的类信息表格
};
*.然后定义一个宏:DECLARE_CLASS(),用来在类里声明RtCIS,(为清晰起见,省略了行末的\ )
#define DECLARE_CLASS(clss)
public:
//类信息,派生自基类CClass
class CClass_##clss:public CClass
{
public:
virtual void *New() { return _pool.Alloc(); }
virtual void Delete(void *p) { _pool.Free((clss*)p); }
CMemPool<clss> _pool;//这个类的内存池
};
static CClass_##clss *_instantiate()
{
static CClass_##clss instance;
instance.SetName(#clss); //设置类的名称,并注册到全局类信息表格中去
return &instance;
}
CClass *GetClass(){return _class;}
static CClass_##clss *_class; //静态变量
*.然后再定义一个宏,用来实现RtCIS:
#define IMPLEMENT_CLASS(clss)
clss::CClass_##clss * clss::_class=clss::_instantiate();//定义并初始化静态变量
*.再定义几个宏用来方便的使用这套系统:
#define Class_New(clss) (clss*)(clss::_class->New())
#define Class_Delete(p) (p)->GetClass()->Delete(p)
#define Class_NewByName(clssname) CClass::NewByName(clssname)
#define Class_GetName(p) (p)->GetClass()->GetName()
*.下面是一个例子,关于水果的:
在头文件里:
class CFruit
{
public:
virtual CClass*GetClass()=0;
//...
//...
};
class CApple:public CFruit
{
public:
DECLARE_CLASS(CApple);
//...
//...
};
class COrange:public CFruit
{
public:
DECLARE_CLASS(COrange);
//...
//...
};
class CGrape:public CFruit
{
public:
DECLARE_CLASS(CGrape);
//...
//...
};
在cpp文件里:
IMPLEMENT_CLASS(CApple);
IMPLEMENT_CLASS(COrange);
IMPLEMENT_CLASS(CGrape);
实际使用:
int main()
{
CFruit* fruit1=Class_New(CApple);
CFruit* fruit2=Class_New(COrange);
CFruit* fruit3=Class_NewByName("CGrape");
assert(std::string("CApple")==Class_GetName(fruit1));
assert(std::string("COrange")==Class_GetName(fruit2));
assert(std::string("CGrape")==Class_GetName(fruit3));
Class_Delete(fruit1);
Class_Delete(fruit2);
Class_Delete(fruit3);
}
另外,这两天在看havok的物理引擎,它的对象分配也采用了类似的方法,并且有一个比较好的特性就是对于某一个类,可以在每一个线程里为它配备一个独立的内存分配器(这样就可以避免在内存分配代码里加锁,有助于提高效率),比如
void thread1()
{
CFruit* fruit1=Class_New(CApple);
Class_Delete(fruit1);
}
void thread2()
{
CFruit* fruit2=Class_New(CApple);
Class_Delete(fruit2);
}
fruit1和fruit2是自动从两个独立的内存池里分配出来的,我还没看havok是怎么实现的,不过应该不会太麻烦.希望将来有时间加这个feature.