文章贴不全,从这下,有代码和本文
http://download.csdn.net/source/2135630
需求
程序员通常有一个说法,叫做数据结构+算法=程序。随着软件项目的日渐复杂,这个公式我认为应该修订成数据结构+算法+构架=程序,而在这个google baidu横行,从蛋糕制作到原子弹设计原理都随时可查的年代,数据结构和算法对于一个成熟的程序员,已经不是主要的难点。于是,好的系统构架和采用的设计模式,成为一个软件的设计流程是否能顺利高效实现的关键。
最近几年,我开始学习和使用.net框架开发游戏项目,.net框架的设计,个人认为真的非常优秀。在.net众多优秀的特性中,我觉得反射,对于系统设计影响极为重大,一个系统是否支持反射(Reflection),甚至会决定系统体系构架。比如在Reflection体系下,实现远程调用(RPC)就会很顺畅,而.net让GUI变得无比痛快的PropertyGrid.SelectObject也是只有在Reflection上才会如此方便强大的实现。
Reflection很诱人,以致于让我一直想在C++上实现这个特性,在C++0x规范难产的情况下,更为激进的Reflection特性很难近几年得到标准C++的支持,于是,我在终于08年初无聊的时候做了一个简单的反射实现。随后,由于没有实际应用,一下就搁置了1年多,最近,我又翻出来这段代码,折腾了一下,相比那时版本,现在的Reflection功能更为强大,容错性更强,而使用起来更加方便。这里在这里除了把最新代码发布出来,还吧一些设计经验教训拿出来和大家分享,也算是应了最开始在blog上说要写相关文章的承诺。
作为一个Reflection体系,参考.net框架,我们需要起码能做如下几件事:
1. 对象动态创建(包括非缺省构造函数调用)
2. 运行时类型信息(获取类,成员变量,函数信息,继承关系等)
3. 类成员函数的Invoke(动态调用成员函数)
基本假设
就算.net系统,也有一些约定是不能违背的,我的Reflection系统也是要建立在一定基本假设上的。
1. 单继承,虽然个人感觉理论上MI支持Reflection也不是不可能,但是java,.net等支持Reflection的体系下,都不支持MI,而只能单继承,我想我还是别去想着怎么搞他了,于是我假定了我的Reflection系统只支持SI,起码ClassType的结构,我就要好写不少。
2. C++编译器起码要支持偏特化,VC6一类的就别来了,不支持偏特化的编译器,作一个选择子都能让人郁闷无比。而TypeList一类的特性,需要编译器在编译时做递归处理,那些古董编译器还是休息下吧。
3. 尽可能的不要改动平时C++类的代码设计和书写习惯。
设计概要
让我们推导一下,为了实现Reflection的那些特性,我们大体需要做一些什么 。首先,我们需要一些数据结构,来描述Class,Member,Methord。然后我们需要有一个流程,能够自动获取我们感兴趣的那些数据,最后,我们需要运行时期处理这些数据。
下面给出部分数据结构的定义
struct VClassType
{
struct VMember
{
VMember( VClassType* t , LPCTSTR n , UINT o , BOOL bC , BOOL bP )
: Type(t)
, Name(n)
, Offset(o)
, IsConst(bC)
, IsPointer(bP)
{
}
~VMember()
{
for( size_t i=0 ; i<Attrs.size() ; i++ )
{
delete Attrs[i];
}
Attrs.clear();
}
VClassType* Type;
VString Name;
UINT Offset;
BOOL IsConst;
BOOL IsPointer;
std::vector<VAttribute*> Attrs;
};
VClassType()
: Type(OT_ReflectObject)
, Super(NULL)
, ClassID(0)
, CreateObject(NULL)
{
}
VClassType( ObjType t , LPCTSTR fn , vIID id ,VClassType* s=NULL )
: Type(t)
, FullName(fn)
, ClassID(id)
, Super(s)
, CreateObject(NULL)
{
}
VFX_API ~VClassType();
VClassType* Super;
ObjType Type;
VString FullName;
vIID ClassID;
std::vector<VMember> Members;
std::vector<VMethodBase*> Methods;
typedef void*(*fnCreateObject)();
fnCreateObject CreateObject;
template< typename type >
type* vfxCreateObject(){
if( CreateObject )
return (type*)CreateObject();
return NULL;
}
ObjType GetSuperType(){
return Type;
}
bool CanConvertTo( VClassType* pType ){
if( this==pType )
return true;
else if( Super!=NULL )
return Super->CanConvertTo(pType);
else
return false;
}
bool CanConvertTo( const vIID& id ){
if( this->ClassID==id )
return true;
else if( Super!=NULL )
return Super->CanConvertTo(id);
else
return false;
}
//可以根据ClassType的类型得到是否ReflectObject,如果是可以启动运行时信息
//如果不是至少可以通过IsPointer检查是否是一个指针
template<class type>
type& GetMember(void* pHostObj , int i){
return (reinterpret_cast<type*>(reinterpret_cast<UINT_PTR>(pHostObj) + Members[i].Offset))[0];
}
};
struct VMethodBase
{
struct ParamType
{
typedef VClassType* VClassTypePtr;
VClassType* ClassType;
BOOL IsConst;
BOOL IsPointer;
BOOL IsRefer;
operator VClassTypePtr (){
return ClassType;
}
};
VMethodBase()
: IsConstructor(false)
{
}
~VMethodBase()
{
for( size_t i=0 ; i<Attrs.size() ; i++ )
{
delete Attrs[i];
}
Attrs.clear();
}
typedef void ( VReflectBase::*FunAdress )();
VString Name;
FunAdress Address;
bool IsConstructor;
ParamType ReturnType;
std::vector<ParamType> ArgTypes;
std::vector<VAttribute*> Attrs;
VFX_API virtual ObjBase* Invoke( VReflectBase* pBindObject , std::vector<ObjBase*>& pArgs ) = 0;
VFX_API virtual void* Constructor( std::vector<ObjBase*>& pArgs ) = 0;
};
定义的数据结构,VClassType内包含了他所有的成员变量VMember,所有的成员函数VMethodBase,这些数据结构定义很简单,看名字就能明白意思。现在我们最大的问题是如果获得这些数据。类型信息VclassType与C++中的Class应该是一一对应的关系,我们应该在系统起来后,为每一个类产生他的所有详细信息。我的方法很简单,利用一个哑元全局变量构造的过程来实现信息自动注册,下面是应用的Example
struct DummyCreator
{
DummyCreator(){
BUILD_CASS(float);
BUILD_CASS(double);
BUILD_CASS(INT8);
BUILD_CASS(INT16);
BUILD_CASS(INT32);
BUILD_CASS(INT64);
BUILD_CASS(UINT8);
BUILD_CASS(UINT16);
BUILD_CASS(UINT32);
BUILD_CASS(UINT64);
BUILD_CASS(VIUnknown);
BUILD_CASS(VObject);
BUILD_CASS(VIObject);
BUILD_CASS(reflect::VAttribute);
BUILD_CASS(VStringA);
BUILD_CASS(VStringW);
BUILD_CASS(vfx::vgc::GCObject);
}
} dummy_obj;
BUID_CLASS是一条宏,他会在dummy_obj构造的时候根据传入的类型,获取指定类型的类型信息并且完成自动注册。因此现在最大的问题就是如果在BUILD_CLASS中实现类信息获取。
结构
整个victory的reflection由几个文件组成,由于采用template+macro实现,主体代码在几个h文件内,这里简单列出他们并且进行一下介绍性描述
vfxTemplateUtil.h:要用到的一些基础辅助模板代码
vfxReflectBase.h:类型信息的数据结构定义,Reflection系统下用到的一些特有特化版本模板实现。
vfxReflectionMethod.h:成员函数信息获取和调用,另外定义了一大堆macro,形成语法糖,方便实际运用。
vfxVictoryCoreAssembly.h:核心类型信息收集,cpp内还有一些测试函数。
Reflection设计的核心和难点就在于成员函数的处理。我的思想就是利用Functor来萃取函数信息,并且方便的调用它们。
技巧
在Reflection的过程,需要编译器做大量编译时期分析,所以要用到很多稀奇古怪的template,有的甚至是一些生僻的C++语法。对于template的一些技巧,vfxTemplateUtil.h里面都有可读性尚好的实现(还是不太好读,毕竟C++template代码本身就是很BT,我这里说的可读性是相对于Boost,Loki一类至尊代码)。
1. Typelist:这个几句话描述清楚真的很难,他的作用是让编译器在编译时期产生一个数组,这个数组装的是类型。
2. Functor:仿函数,这玩艺的作用是把一个全局或者成员函数包装起来,
3. 萃取:TypeTraits,IsAbstract等等
Typelist是我Functor的基础,一个Functor,可调用体,参数个数是不定的,为了解决在template实现特化,Typelist可以用一个编译器类型包含多个参数类型,比较完美的解决参数个数变化的问题,ConstructorTraits,Functor都利用了这个特性。而Functor中用到的SafeTListIndexOf是Typelist的TListIndexOf的一个增强,他利用了Selector原理,实现了Typelist下标访问越界的安全处理,他的实现,依托于TlistLength,这几个东西,都是依赖于编译期递归。如果Typelist内包含的类型个数不足,编译器将返回NullObject这个自定义的非法类型。
Reflection一个重要的特性对象构造,需要获得对象构造过程,以获得动态对象创建能力,比如ClassFactory设计。C++中new Type(args)语法可以分配对象,并且调用对应版本构造函数,构造函数也是一个函数,理论上他也是一个地址,可以call,但是它没有返回值,所以typedef (Classname::*ClassName)(…