C++反射機制的實現
前幾天用C++為《捕魚達人》移植UI編輯器的時候,遇到了幾個難點。一個是通過類名的字串建立相應的類的例項化。還有一個是通過屬性的名字字串來操作相應的類的屬性。用支援反射的Objective-C或者Java語言來實現類似功能是非常簡單的。但是C++不支援,糾結了幾天,終於實現了類似於反射的功能。
思路分為以下幾步:
1、在要反射的類中定義一個回撥函式,用來建立這個類的例項;
2、設計一個工廠類,類中有一個std::map,用於儲存類名和建立例項的回撥函式。通過類工廠來動態建立類物件;
3、程式開始執行時,將回調函式存入std::map(雜湊表)裡面,類名字做為map的key值;
下面我來一步一步的講解具體的實現方法。
首先宣告一個回撥函式
- typedefvoid* (*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 ;
- } ;
這裡把工廠類設計成單件類,通過靜態函式sharedClassFactory()來呼叫類中方法,靜態類例項保證了類在開始執行前就存在。
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來動態建立類的屬性,動態建立屬性類似於上面的動態建立類。
下面先帖程式碼:
- typedefvoid (*setValue)(CKBaseClass *t, void* c) ;
- class CKBaseClass
- {
- private:
- DECLARE_CLASS(CKBaseClass)
- public:
- CKBaseClass() {}
- virtual ~CKBaseClass() {}
- staticvoid* createInstance() {returnnew CKBaseClass();}
- virtualvoid registProperty() {}
- virtualvoid display() {}
- map<string, setValue> m_propertyMap ;
- } ;
- #define SYNTHESIZE(classType, varType, varName) \
- public: \
- inlinestaticvoid 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(){}
- staticvoid* createInstance()
- {
- returnnew CKHelloClass() ;
- }
- virtualvoid registProperty()
- {
- m_propertyMap.inset(pair<string, setValue>("setm_pValue", setm_pValue)) ;
- }
- virtualvoid 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,稍微修改工程,即可實現多平臺編譯。
謝謝大家。