文章貼不全,從這下,有代碼和本文
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)(…