最近在看MFC的代碼,雖然這破玩意,老朽已經(jīng)很熟悉了得不能再熟悉了,但是這些破代碼自由其獨(dú)有的吸引力,不說(shuō)別的,單單理解起來(lái)就非常容易,比之什么boost代碼容易看多了,單步調(diào)試什么的,都非常方便,堆棧上一查看,層次調(diào)用一目了然。一次又一次地虐這些曾經(jīng)虐過(guò)老朽的代碼,也是人生快事一件。平心而論,mfc的代碼還是寫得挺不錯(cuò)的,中規(guī)中矩,再加上過(guò)去九十年代之初,16位的windows系統(tǒng),那個(gè)時(shí)候的面向?qū)ο蟮腸++的風(fēng)靡一時(shí),完全采用標(biāo)準(zhǔn)c++,能做成這樣,實(shí)屬難能可貴,也在情理之內(nèi)。并且,后面壓上com之后,mfc也不崩盤,采用內(nèi)嵌類實(shí)現(xiàn)的com的方式,也很有意思。然后,從mfc中也能學(xué)到不少windows gui的使用方式還有各種其他雜七雜八東西,雖然win32已經(jīng)沒(méi)落。但是里面的技術(shù)還是挺吸引人,可以消遣也不錯(cuò)。當(dāng)然,對(duì)于新人,mfc不建議再碰了,mfc真是沒(méi)飯吃的意思。你想想,一個(gè)gui框架,沒(méi)有template可用的情況下,而逆天c++11的lambda作為匿名functor,更加不必提了,只有虛函數(shù)和繼承可用,也沒(méi)有exception,能搞成mfc這副摸樣,的而且確是精品。其實(shí),后來(lái)的巨硬也有救贖,看看人家用template做出來(lái)的專為com打造的atl又是什么樣子呢,然后建構(gòu)在atl的windows thunk基礎(chǔ)上開發(fā)的wtl又是怎樣的小巧玲瓏。巨硬在template上的使用還是很厲害的,atl將template和多繼承用的真是漂亮。人家?guī)资昵熬蛯emplate和多繼承用得如此出神入化,反觀國(guó)內(nèi),一大批C with class又或者狗粉一再叫囂template滾出c++,多繼承太復(fù)雜了,運(yùn)算符重載不透明,心智負(fù)擔(dān),隱式類型轉(zhuǎn)換問(wèn)題太多,virtual是罪惡之源萬(wàn)惡之首,構(gòu)造函數(shù)析構(gòu)函數(shù)背著馬猿做了太多事情,exception對(duì)代碼沖擊太大,打斷代碼正常流行,時(shí)時(shí)刻刻都好像隱藏著不定時(shí)炸彈。依本座看來(lái),C++中一大批能夠顯著減少重復(fù)代碼,帶來(lái)類型安全的拔高抽象層次的好東西,對(duì)于這些C語(yǔ)言衛(wèi)道士而言,都是混亂之物。其實(shí),c語(yǔ)言就是一塊廢柴,抽象層次太低,可以做文章的地方太少了。
就以構(gòu)造函數(shù)和類型轉(zhuǎn)換operator為例,來(lái)看看怎么用于C的char *itoa(int value, char *str, int radix)。
itoa的參數(shù)之所以還需要str入?yún)ⅲ鞘且驗(yàn)镃語(yǔ)言中缺乏返回?cái)?shù)組的語(yǔ)言元素,所以調(diào)用者要提供一個(gè)字符數(shù)組作為緩沖用于存放結(jié)果,但是這個(gè)多出來(lái)str參數(shù)真是沒(méi)必要啊,因?yàn)檎Z(yǔ)言能力的欠缺,所以只好把這個(gè)負(fù)擔(dān)壓到猿猴身上。也有些itoa的實(shí)現(xiàn)沒(méi)有這個(gè)累贅str的入?yún)ⅲ莾?nèi)部static的字符數(shù)組,用于存放結(jié)果并返回給上層。這個(gè)版本就只有兩個(gè)入?yún)⒘耍且蔡话踩耍瑒e提多線程了。假如,有一個(gè)函數(shù)fn(char* str1, char* str2),然后,這樣調(diào)用fn(itoa(num1),itoa(num2)),畫面太美了。另外,那個(gè)有多余str參數(shù)版本的itoa也好不到哪里去,要?jiǎng)谛馁M(fèi)神準(zhǔn)備兩塊字符數(shù)組,然后還要保證參數(shù)傳遞的時(shí)候不要一樣。反正C語(yǔ)言的粉絲整天很喜歡寫這些重復(fù)代碼,并且美其名曰掌控一切細(xì)節(jié)的快感。
請(qǐng)看構(gòu)造函數(shù)和類型轉(zhuǎn)換operator怎么解決。太easy了。
struct ToString
{
char text[28];
int length;
ToString(int n)
{
//轉(zhuǎn)換字符串,結(jié)果存放于text中
}
operator const char*()
{
return text;
}
};
并且,這里的ToString還可以安全的用之于printf里面呢,因?yàn)樗旧砭褪亲址幕怼槭裁词荰oString,因?yàn)樗粌H僅要它ToString int,還有double,bool,char,……
不好意思,扯遠(yuǎn)了,只是想說(shuō),框架或者函數(shù)庫(kù)的表現(xiàn)能力也要取決于語(yǔ)言本身的表達(dá)能力。就好像C#就可以做出linq那樣爽快的框架,java再怎么拼命也搗鼓不出來(lái)一個(gè)一半好用的linq,C++也不行,但是C++可以搗鼓類似于haskell中的map,filter,fold等, 并結(jié)合linq的后綴表達(dá)方式。就好比下面這樣
vector<int> nums = {...}
Range(nums).Map(_1 * _1).Filter(_1 % 2).CopyTo(dest); // 用了boost中的lambda表達(dá)法,因?yàn)榈拇_很簡(jiǎn)潔,沒(méi)法控制。對(duì)于復(fù)雜情況,當(dāng)然要用C++11原生的lambda
勉勉強(qiáng)強(qiáng)差可滿足吧。如果C++的lambda參數(shù)可以自動(dòng)推導(dǎo)就好了,不過(guò)也沒(méi)什么,主要是ide下用得爽。用泛型lambda也能將就。
所以,回過(guò)頭來(lái),再看看mfc(沒(méi)飯吃),就可以了解其各種隱痛了。真的,以90年代的眼光來(lái)看,mfc真是做到極致了。mfc不可能走win32下窗口函數(shù)C語(yǔ)言那樣的消息發(fā)送消息反應(yīng)的正路(邪路)吧。窗口函數(shù)這一套,在90年代面向?qū)ο笫⑿械臅r(shí)代,絕對(duì)不能被忍受,只是到了前幾年,才被發(fā)現(xiàn)其價(jià)值原來(lái)不菲,這是解耦合砍繼承樹的好手法,老朽在前幾年也跟風(fēng)吹捧窗口函數(shù)的那一套。平心而論,smalltalk的這一套消息發(fā)送的動(dòng)態(tài)語(yǔ)言,確實(shí)是很強(qiáng)有力的抽象手段,我不管目標(biāo)對(duì)象能否反應(yīng)該消息,閉著眼睛就可以給你發(fā)送消息,你能反應(yīng)就反應(yīng),不能反應(yīng)就拉倒,或者調(diào)用缺省的反應(yīng)方式,就好像DefWindowProc(職責(zé)鏈模式?),又或者是拋出異常,怎么做都可以。一下子就解開了調(diào)用者和目標(biāo)對(duì)象的類型耦合關(guān)系。面向?qū)ο笾校l(fā)送和消息反應(yīng)才是核心,什么封裝繼承多態(tài),那是另一套抽象方式,雖然坊間說(shuō)這也是面向?qū)ο蟮幕疽兀遣皇牵?dāng)然,這或許也只是個(gè)人觀點(diǎn)。
或許,從某種意義上講,C++/java/C#一類的成員函數(shù)調(diào)用形式,其實(shí)也算消息發(fā)送吧。比如,str.length(),就是給對(duì)象str發(fā)送length的消息,然后str收到length消息,作出反應(yīng),執(zhí)行操作,返回里面字符串的長(zhǎng)度。靠,這明明就是直接的函數(shù)調(diào)用,搞什么消息發(fā)送的說(shuō)辭來(lái)強(qiáng)辯,顛倒是非黑白,指鹿為馬。可不是嗎?編譯器知道str是字符串類型,知道length成員函數(shù),馬上就生成了高效的函數(shù)調(diào)用方式。在這里,沒(méi)有任何動(dòng)態(tài)多態(tài)的可能,發(fā)生就發(fā)生了,一經(jīng)調(diào)用,動(dòng)作立馬就行動(dòng),沒(méi)有任何商量的余地。耦合,這里出現(xiàn)強(qiáng)耦合,調(diào)用者和str綁在一塊了,假如以后出現(xiàn)更高效率更有彈性的string的代替品了,可是沒(méi)法用在這里了,因?yàn)檫@里str.length()的綁定很緊很緊。
人家消息發(fā)送就不一樣了,動(dòng)態(tài)的,可以動(dòng)態(tài)替換對(duì)象,替換方法,彈性足足。并且,消息發(fā)送的模式下,對(duì)象收到消息,要判斷消息,解析消息,找到消息的執(zhí)行函數(shù),最后才終于執(zhí)行任務(wù)。這么多間接層,每一層都可以做很多很多文章。比如,在消息到達(dá)對(duì)象之前做文章,就可以搞消息隊(duì)列,把消息和參數(shù)暫存起來(lái),這個(gè)時(shí)候,什么actor模式就大放異彩,至于undo,redo,更加是小菜一碟。然后呢,再給對(duì)象安裝消息解析器,把消息和消息參數(shù)轉(zhuǎn)換成其他類型消息。比如原本對(duì)象不能反應(yīng)這條消息,但是對(duì)消息參數(shù)稍加修飾,然后在發(fā)送給對(duì)象,這不就是適配器模式。總之,可操作可挖掘的空間太大了,遠(yuǎn)遠(yuǎn)不止23條。
但是,封裝繼承多態(tài)就一無(wú)是處了嗎?不是的,最起碼一點(diǎn),編譯期間可以報(bào)錯(cuò)。因?yàn)榈拇_有很多時(shí)候,我們明明就知道對(duì)象的類型,明明就知道對(duì)象不可能是其他類型,比如字符串,比如復(fù)數(shù),比如數(shù)組這些玩意,無(wú)論如何,它們都不需要?jiǎng)討B(tài)消息的能力。我們就知道手上的對(duì)象就是字符串就是復(fù)數(shù),不可能是別的,并且,我們就是要明確地調(diào)用length函數(shù)。我們就是要編譯器幫忙檢查這里潛在的語(yǔ)法類型錯(cuò)誤,比如對(duì)復(fù)數(shù)對(duì)象調(diào)用length函數(shù),編譯器馬上就不高興了。并且,一切都是確定的,所以編譯器也能生成高效的代碼,高效的不能再高效了。對(duì)此,消息發(fā)送的面向?qū)ο缶妥霾坏搅耍还苁鞘裁磳?duì)象,int,string,complex種種,都來(lái)個(gè)消息發(fā)送。這樣一來(lái),靜態(tài)類型檢查和高效的代碼,就木有了。
考察一下,面向?qū)ο笥械燃?jí)之分,一步一步,有進(jìn)化的階梯。每進(jìn)化一次,就多了一層間接,類型耦合就降低,就進(jìn)一步超越編譯器的限制,當(dāng)然,也意味著編譯器幫忙檢查類型錯(cuò)誤生成高效代碼就弱了一分。事情往往就是,有所得必有所失。少即是多,多即是少。因此,可推得少即是少,多即是多。少始終是少,多始終是多。
一切,還是要從C語(yǔ)言說(shuō)起,C語(yǔ)言中,沒(méi)有class,沒(méi)有函數(shù)重載。函數(shù)名是什么,最后就是什么。在這種條件下,代碼多了,每個(gè)新的函數(shù)名字要考究半天,一不小心,要么函數(shù)名字就會(huì)很長(zhǎng),要么函數(shù)名字短了要沖突或者不好理解。但是好處是,最后生成目標(biāo)代碼時(shí),什么函數(shù)名字就是什么名字,所見(jiàn)即所得,沒(méi)有異常,不會(huì)搗鬼,于是其他各種語(yǔ)言都可以高高興興開開心心調(diào)用。猿猴觀碼,也很清晰。C++也是在這里賺了第一桶金。其實(shí),這么苛刻的條件下,最考究猿猴的代碼架構(gòu)能力,架構(gòu)稍微不好,最后都勢(shì)必提早崩掉,前期就可以過(guò)濾很多垃圾架構(gòu)。
然后就是C with class了,開始在函數(shù)名字上面做文章了。同一個(gè)函數(shù)名字依對(duì)象類型,開始擁有靜態(tài)多態(tài)能力了。比如,str.length(),這是對(duì)字符串求長(zhǎng)度。數(shù)組的變量,nums.length(),對(duì)數(shù)組求長(zhǎng)度。同一個(gè)length的名字,用在不同的對(duì)象上,就有不同的意義。這如何做到呢,最初,cfront(第一版C++編譯器)的處理方式是,可以說(shuō)是語(yǔ)法糖,就是在名字和調(diào)用形式上做文章,比如,str.length(),變成,string_length(&str),array_length(&nums)。別小看這點(diǎn)小把戲語(yǔ)法糖,這真是有力的抽象手法。不說(shuō)別的,就說(shuō)起名字吧,可以統(tǒng)一length了,無(wú)須費(fèi)思量string_length,list_length了。然后,對(duì)象統(tǒng)一調(diào)用方式,str.length(),list.length(),函數(shù)確定這種吃力不討好的事情就交給編譯器去做好啦,解放部分腦細(xì)胞。這,的確很好,但是,全局函數(shù)是開放式的,而對(duì)象的成員函數(shù)是封閉的,一旦class定義完畢,成員函數(shù)的數(shù)量也就定死了。猿猴最講究東西的可擴(kuò)展性,不管成員函數(shù)多么方便多么抽象有力,就擴(kuò)展性而言,就差了一大截,其他優(yōu)勢(shì)都彌補(bǔ)不了。語(yǔ)義上看,擴(kuò)展成員函數(shù)的語(yǔ)法完全與原生的一樣,增加一個(gè)簡(jiǎn)單的語(yǔ)法形式來(lái)擴(kuò)充,但是多年下來(lái),標(biāo)準(zhǔn)委員會(huì)都不務(wù)正業(yè),哎。顯然,編譯器的類型檢查能力和生成的代碼性能,沒(méi)有任何減少,但是,猿猴看代碼,不能再所見(jiàn)所得了,必須根據(jù)對(duì)象類型,才能確定最終的目標(biāo)函數(shù)。就這么點(diǎn)小改進(jìn),當(dāng)時(shí)C++馬上就展示其驚人的吸引力。假如,C++只能留在這一層,相信到今天為止,可以吸引到更多的c粉。可是,C++開始叛變。
C++的函數(shù)重載,還有操作符重載,外加隱式類型轉(zhuǎn)換和隱式構(gòu)造函數(shù),還有const,volatile修飾,當(dāng)然,代碼可以寫得更加簡(jiǎn)潔,編譯器可以做的事情也更多啦,但是函數(shù)的調(diào)用再也不明確了。部分專注于底層的猿猴的弱小的抽象能力把控不住了,不少人在這里玩不動(dòng)了。此外,命名修飾把最終函數(shù)名字搞得亂七八糟,二進(jìn)制的通用性也要開始廢了。導(dǎo)致C++的dll不能像C那樣到處通吃。像是狗語(yǔ)言就禁止函數(shù)重載這個(gè)功能。大家好像很非難C++的操作符重載,但是haskell還能自定義新的操作符呢。雖然在這里,編譯器還能生成高效代碼,但是,各種奇奇怪怪類型轉(zhuǎn)換的規(guī)則,編譯器也偶爾表現(xiàn)出奇,甚至匪夷所思,雖然一切都在情理之內(nèi)。
其實(shí),不考慮什么動(dòng)態(tài)能力,單單是這里的靜多態(tài),基于對(duì)象(俗稱ADT)的抽象模式,就可以應(yīng)付70%以上的代碼了。想想以前沒(méi)有靜多態(tài)的C日子是怎么過(guò)的。
此時(shí),開始兵分兩路,C++一方面是動(dòng)多態(tài)發(fā)展,表現(xiàn)為繼承,多繼承,虛繼承,虛函數(shù),純虛函數(shù),rtti(廢物,半殘品),到此為止了,止步不前;另一方面是繼續(xù)加強(qiáng)靜多態(tài),王者,template,一直在加強(qiáng),模板偏特化,template template,varidiac tempalte,consexpr, auto,concept,……,背負(fù)著各種指責(zé)在前進(jìn),就是在前進(jìn)。C++企圖以靜態(tài)能力的強(qiáng)悍變態(tài)恐怖,不惜榨干靜態(tài)上的一點(diǎn)點(diǎn)可為空間,累死編譯器,罔顧邊際效應(yīng)的越來(lái)越少,企圖彌補(bǔ)其動(dòng)態(tài)上的種種不足。這也是可行的,畢竟haskell都可以做到。template的話題太龐大了,我們言歸正傳,面向?qū)ο蟆?br /> 下面就是被指責(zé)得太多的C++多繼承,虛函數(shù),RTTI,脆弱的虛函數(shù)表,等,這些說(shuō)法,也都很有道理,確是實(shí)情,兼之C++沒(méi)有反射,沒(méi)有垃圾回收,用上面這些破玩意搗鼓,硬著頭皮做設(shè)計(jì)做框架,本來(lái)就先天能力嚴(yán)重不足,還要考慮內(nèi)存管理這個(gè)大敵(循環(huán)引用可不是吹的),更有exception在旁虎視眈眈,隨時(shí)給予致命一擊。更要命的是,多繼承,虛函數(shù),虛繼承,這些本來(lái)就殺敵八百自傷一千,嚴(yán)重?cái)_亂class的內(nèi)存布局,你知道vector里面隨隨便便插入元素,對(duì)于非pod的元素,不僅僅是移動(dòng)內(nèi)存,騰出新位置來(lái)給新對(duì)象安營(yíng)扎寨,還要一次又一次地對(duì)被移動(dòng)的對(duì)象執(zhí)行析構(gòu)拷貝構(gòu)造。沒(méi)有這些奇奇怪怪的內(nèi)存布局,vector的實(shí)現(xiàn)應(yīng)該會(huì)清爽很多。稍微想想,這實(shí)在太考究猿猴的設(shè)計(jì)能力,其難度不亞于沒(méi)有任何多態(tài)特性的C語(yǔ)言了。可以這么說(shuō),繼承樹一旦出現(xiàn)虛繼承這個(gè)怪胎,整體架構(gòu)就有大問(wèn)題,毫無(wú)疑問(wèn),iostream也不例外。不過(guò),如果沒(méi)有那么多的動(dòng)態(tài)要求,好比gui框架的變態(tài)需求,嚴(yán)格以接口作為耦合對(duì)象,輔以function,也即是委托,又可以應(yīng)付多起碼15%的局面。其實(shí),必須要用到virtual函數(shù)的時(shí)候,將virtual函數(shù)hi起來(lái),那種感覺(jué)非常清爽,很多人談virtual色變,大可不必。C#和java還加上垃圾回收和反射,這個(gè)比例可以放大很多。在這種層次下,接口最大的問(wèn)題是,就好像成員函數(shù),是封閉的。一個(gè)class定義完畢,其能支持的interface的數(shù)量也就定死了,不能再有任何修改。interface可以說(shuō)是一個(gè)class的對(duì)外的開放功能,現(xiàn)實(shí)世界中,一種東西的對(duì)外功能并不是一開始就定死了的,其功能也在后來(lái)慢慢挖掘。但是,C++/java/C#的接口就不是這樣,class定義完畢,就沒(méi)有任何潛力可言了。明明看到某些class的能力可以實(shí)現(xiàn)某些接口,甚至函數(shù)簽名都一樣,對(duì)不起,誰(shuí)讓你當(dāng)初不實(shí)現(xiàn)這個(gè)接口。對(duì)此,各種動(dòng)態(tài)語(yǔ)言閃亮登場(chǎng),或mixing或鴨子類型。接口還有另一尷尬之處,比如,鳥實(shí)現(xiàn)了會(huì)飛的接口,鴨子企鵝也繼承了鳥,自然也就繼承了會(huì)飛的接口,沒(méi)辦法不繼承。面對(duì)著一個(gè)需要IFlyable參數(shù)的函數(shù),我們順利的傳一只企鵝進(jìn)去,然后企鵝再里面始終飛不起來(lái),就算企鵝在被要求飛的時(shí)候,拋出異常,也不過(guò)自欺欺人。這種悲劇,就好像有些人很會(huì)裝逼,最后一定會(huì)壞事。搞出接口這種破事,就是為了讓編譯器做類型檢查的。又有人說(shuō),bird應(yīng)當(dāng)分為兩類,會(huì)飛的和不會(huì)飛的,這的確能解決飛行的尷尬。但是,有很多鳥具備捉蟲蟲的能力,然后又有那么一小撮鳥不會(huì)捉蟲只會(huì)捉魚,難道又要依據(jù)捉蟲能力再劃分出鳥類。于是鳥類的繼承樹越長(zhǎng)越高,畫面越來(lái)越美。這分明就是語(yǔ)言能力的不足,把問(wèn)題交給猿猴了。請(qǐng)謹(jǐn)記,接口就是一個(gè)強(qiáng)有力的契約,既然實(shí)現(xiàn)了一個(gè)接口,就說(shuō)明有能力做好相關(guān)的事情。再說(shuō),既然interface這么重要,于是我們?cè)僭O(shè)計(jì)class的時(shí)候,就自然而然把精力放在interface這個(gè)對(duì)外交流媒介的手段之上了,而忽視了class本身的推敲。class最重要的事情就是全心全意做好獨(dú)立完整最小化的事情,其他什么對(duì)外交互不要理會(huì)。一個(gè)class如果能夠完整的封裝一個(gè)清晰的概念,后面不管怎么重構(gòu),都可以保留下來(lái)。但是,interface會(huì)分散這種設(shè)計(jì)。接口的悲劇就在于企圖頂多以90分的能力去干一百分的事情,并且還以為自己可以做得好,硬上強(qiáng)干,罔顧自身的極限。往往做了90%的工作量,事情恰恰就壞在剩下來(lái)的10%上。
于是,狗語(yǔ)言走上另一條邪路,鴨子類型。只要class,不,是struct,這種獨(dú)特關(guān)鍵字的品味,只要某個(gè)struct能夠完全實(shí)現(xiàn)某個(gè)interface的所有函數(shù),就默認(rèn)其實(shí)現(xiàn)了這個(gè)接口。并且,狗語(yǔ)言還禁止了繼承,代之以“組合”這個(gè)高大上的名詞了,但是,細(xì)究一下語(yǔ)義和內(nèi)存布局(忽略虛函數(shù)表指針),你媽的,不就是一個(gè)沒(méi)有virtual繼承的弱多繼承嗎?顯式的繼承消失了,隱式的繼承還存在的,好了,還不讓你畫出繼承樹關(guān)系圖,高高興興對(duì)外宣稱沒(méi)有繼承了,沒(méi)有繼承并不表示繼承的問(wèn)題木有存在。但是,因?yàn)楣氛Z(yǔ)言的成員函數(shù)方法可以定義在class,不,struct外面,其擴(kuò)展性就非常好了,對(duì)于一個(gè)interface,有哪些方法,本struct不存在,就地給它定義出來(lái),然后,struct就輕松的實(shí)現(xiàn)了該接口,即使原來(lái)的struct不支持該接口,以后也有辦法讓它支持,很好很強(qiáng)大。之所以能做到這一點(diǎn),那是因?yàn)楣氛Z(yǔ)言的虛函數(shù)表是動(dòng)態(tài)生成的。小心的使用接口各種名字,部分人應(yīng)該狗語(yǔ)言用起來(lái)會(huì)相當(dāng)愉快。可是,你媽,不同接口的函數(shù)名字不能一樣啊,或者說(shuō),同一個(gè)函數(shù)的名字不能出現(xiàn)在不同接口中。不過(guò),這個(gè)問(wèn)題并不難,不就是不一樣的名字嗎,c語(yǔ)言中此等大風(fēng)大浪猿猴誰(shuí)沒(méi)有見(jiàn)識(shí)過(guò)。對(duì)于狗語(yǔ)言,不想做太多評(píng)斷,只是,其擴(kuò)展性確實(shí)不錯(cuò),非侵入式的成員函數(shù)和非侵入式的接口,理應(yīng)能更好地應(yīng)付接口實(shí)現(xiàn)這種多態(tài)方式,只是,編譯器在上面所做的類型約束想必會(huì)不如后者,重構(gòu)什么的,想必不會(huì)很方便。自由上去了,約束自然也下來(lái)了。聽起來(lái)挺美,但是內(nèi)里也有些地方要推敲,反正老朽不喜歡,以后也不大會(huì)用上,當(dāng)然,給money自然會(huì)用,給money不搞c++都沒(méi)問(wèn)題。老朽還是比較喜歡虛函數(shù)的接口,更何況c++通過(guò)奇技淫巧也能非侵入式的給class添加接口。在靜態(tài)語(yǔ)言中搞這種鴨子類型的動(dòng)態(tài)語(yǔ)言接口,顯得有點(diǎn)不倫不類。
然后就是com接口的面向?qū)ο螅耆崛ゾ幾g器對(duì)接口類型的約束,自然能換來(lái)更大的自由。由于com的語(yǔ)言通用性目標(biāo),所以搞得有點(diǎn)復(fù)雜,但是com背后的理念也挺純潔。老朽猜測(cè)com好似是要在靜態(tài)語(yǔ)言上搭建出一個(gè)類似于動(dòng)態(tài)語(yǔ)言的運(yùn)行平臺(tái),外加語(yǔ)言通用性。其契約很明確,操作對(duì)象前時(shí),必須先查詢到對(duì)象支持的接口,進(jìn)而調(diào)用接口的函數(shù)。這里有意思的地方在于面對(duì)著一個(gè)com對(duì)象,你居然沒(méi)有辦法知道到它究竟實(shí)現(xiàn)了多少接口。
最后就是消息發(fā)送了,其能力之強(qiáng)大,誰(shuí)用誰(shuí)知道。原則上講,可以看成對(duì)象擁有的虛函數(shù)表的方法無(wú)窮多,又可以把每一條消息看成一個(gè)接口,那么,對(duì)象可能就實(shí)現(xiàn)了無(wú)窮多的接口。你說(shuō),面對(duì)著這樣對(duì)象,還有什么做不出來(lái)呢。真用上消息發(fā)送這種隱藏?zé)o數(shù)間接層,就沒(méi)有什么軟件問(wèn)題解決不了的。任何軟件問(wèn)題不就是通過(guò)引入間接層來(lái)解決的嘛。現(xiàn)在用上消息發(fā)送這種怪物,就問(wèn)你怕不怕。沒(méi)有免費(fèi)午餐,自然要付出類型安全的危險(xiǎn)和性能上的損失。