閱讀564 返回首頁    go 技術社區[雲棲]


偷Microsoft師學MFC藝:且看C++如何支持反射

如果你問一個IT人士“C++如何實現類似Java的反射?”,結果會怎樣呢?~@#¥%……&*,估計大部分人都會要稍微思考了一下,或者直接說“C++根本就不支持反射的呀!”。

是的,C++語言本身是不支持反射的,但實際應用中總是會有將對象序列化的需求,總不可能C++不支持,我們就不用C++了,既然發明C++的大師們沒有考慮這個,那我們隻有自己動手了,毛主席說過“自己動手,豐衣足食”!

天生限製

C++語言本身不支持反射機製,但C++對象總是要序列化的,序列化就是存儲到磁盤上,將對象變成一定格式的二進製編碼,然後要用的時候再將保存在磁盤上的二進製編碼轉化成一個內存中的對象,這個過程中總是需要有一個指示來告訴編譯器要生成什麼樣的對象,最簡單的方式當然就是類名了,例如:將一個ClassXXX對象存儲到磁盤上,再從磁盤讀取的時候讓編譯器根據“ClassXXX”名稱來new一個對象。

但是問題出現了,C++語言本身不支持反射,也就是說不能通過如下方式生成一個對象:

ClassXXX object = new ClassXXX”;

 

工廠方法

當然,這樣的方法不行,那我們隻有另辟蹊徑。最簡單的就是工廠方法了:

ClassXXX* object = FactoryCreate(“ClassXXX”);

至於FactoryCreate的設計就很簡單了,if的集合就可以了:

if(name = “ClassXXX”)

return new ClassXXX;

if(name = “ClassYYY”)

return new ClassYYY;

 

看起來不錯,來個類名就可以生成對應的對象,功能上解決了根據類名生成對象的問題。

假如以上所有的代碼都有你一個人編寫,那當然問題不大,但是假如有一天你的公司擴大了,這部分代碼由兩個不同的組AB來維護,啊哈,問題來了,A組每添加或者修改一個類,都要通知B組更新FactoryCreate函數,也就是說A組的任何關於類的修改,都需要B組來修改,但實際上B的修改不產生任何價值,而且不勝其煩,永無止盡!!如果哪天來了一個新員工,由於對這個規定還不清楚,忘記了通知,那就完了:編譯通不過!

一個公司內都會產生如此多的問題,更何況微軟這樣的大公司是麵對全球的各種各樣的客戶,如果微軟把這部分做進框架代碼中,嗬嗬,那微軟所有的人不用幹其他事情了,每天處理來自全球的要求修改FactoryCreate函數的郵件和電話就夠他們忙的了:)

 

回調工廠

既然此路不好走,那麼我們再考慮其它方法吧,一個可選的方法是將FactoryCreate做成回調函數,框架提供注冊接口RegisterFactoryCreate,框架函數如此實現:

typedef CObject* *FactoryCreate_PTR(String name);

RegisterFactoryCreate(FactoryCreate_PTR fc_ptr);

應用代碼如此實現:

CObject* MyFactoryCreateString name;

RegisterFactoryCreate(MyFactoryCreate);

到這裏一個框架和應用分離的反射機製基本實現了,你是否長籲一口氣,然後準備泡杯咖啡,稍微放鬆一下呢?確實可以稍微休息一下了,畢竟我們完成了一件非常了不起的事情,讓C++實現了反射。

 

但你隻悠閑了一兩天,麻煩事就來了。員工張三跑來向你抱怨“老大,李四注冊的反射函數把我的覆蓋了”!哦,你仔細一看,My god,這個注冊函數隻能注冊一個反射函數,後注冊的就把前麵的覆蓋了!

怎麼辦?總不可能又要求所有的類的反射函數都在一個工廠裏實現吧,那這樣就又回到了工廠方法中描述的時代了。

當然,聰明的你估計很快就能想出問題的解決方法,將RegisterFactoryCreate函數稍加修改就能滿足要求了,新的實現如下:

RegisterFactoryCreateFactoryCreate_PTR fc_ptrString className

然後要求每個類都單獨寫自己的FactoryCreate_PTR函數,類似如下方式:

static CObject* ClassXXX::CreateClassXXX (){

       return new ClassXXX;

};

 

static CObject* ClassYYY::CreateClassYYY(){

       return new ClassYYY;

};

 

到此為此終於大功告成,通過我們的智慧實現了C++的反射功能!一股自豪感油然升起:)

 

最後的殺手鐧:宏

當你為自己的聰明才智而驕傲的時候,那邊卻有幾個開發的兄弟在發出抱怨“唉,這麼多相似的函數,看著都眼花,每個類都要寫,煩死了”。

或者有一天,你要在每個類的CreateClass函數中增加一個其它功能(例如日誌),那麼開發的兄弟真的是要煩“死了”!!!

 

其實仔細一看,包括函數申明、函數定義、函數注冊,每個類的代碼除了類名外其它都是一模一樣的,有沒有簡單的方法呢?

肯定是有的,這個方法就是宏了,按照如下方法定義宏:

#define DECLARE_CLASS_CREATE(class_name) /

static CObject* CreateClass## class_name ();

 

#define IMPL_CLASS_CREATE(class_name) /

static CObject* CreateClass## class_name (){  /

       return new class_name;             /

};

 

#define REG_CLASS_CREATE(class_name) /

RegisterFactoryCreate(class_name::CreateClass## class_name, #class_name);

注:##是連接符,將兩個字符串連接起來,#是將class_name作為字符串處理。

 

大家可以比較一下,用了宏和不用宏是不是代碼感覺完全不一樣呢?而且那天需要增加一個簡單的功能,隻需要改宏定義就ok了,不要全文搜索所有相關函數,然後一個一個的重複添加。

 

到這裏才真正是大功告成!!

 

後記

某天分析SpringIOC時,看到Digester最後利用的實際上是Java的反射機製來根據XML文件定義生成Java對象,突發奇想:如果是C++該怎麼辦?

於是自己就開始分析起來,分析了一段時間突然想起微軟的MFC不正是要支持C++對象序列化的嗎?

趕緊打開深入淺出MFC,重新將這部分研究了一下。看到微軟的天才們在MFC中用宏來實現RTTIDynamic CreateSeralize功能時,我反過來思考“如果是我,我會如何設計?”、“為什麼會這麼設計”?然後一一分析這些各種可能的實現方式,一步一步的推導,最後發現竟然自然而然的就推出了MFC的這種實現方式!

當然,MFC的實現代碼和我給出的代碼不一樣(注冊方式不一樣),但設計思想是一樣的,各位看官可以自行稍加分析就明白了。

MFC的詳細實現可以參考侯捷的《深入淺出MFC》。

 

最後更新:2017-04-02 00:06:43

  上一篇:go 我的opengl編程學習(一)(簡介、繪製圖像、三維觀察、光照)
  下一篇:go 自動將漢字轉換成拚音第一個字母