天天看點

标準C++實作反射特性

文章貼不全,從這下,有代碼和本文

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)(…