天天看點

偷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;

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

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

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

回調工廠

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

typedef cobject* (*factorycreate_ptr)(string name);

registerfactorycreate(factorycreate_ptr fc_ptr);

應用代碼如此實作:

cobject* myfactorycreate(string name);

registerfactorycreate(myfactorycreate);

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

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

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

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

registerfactorycreate(factorycreate_ptr fc_ptr,string 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了,不要全文搜尋所有相關函數,然後一個一個的重複添加。

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

後記

某天分析spring的ioc時,看到digester最後利用的實際上是java的反射機制來根據xml檔案定義生成java對象,突發奇想:如果是c++該怎麼辦?

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

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

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

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