• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆-59  評論-36  文章-0  trackbacks-0

            轉自:http://blog.csdn.net/myan/archive/2010/10/09/5928531.aspx
            作者:孟巖
            --------------------------------------------------------------------------------------
            這是那篇C++0X的正文。太長,先寫上半部分發了。

            Function/bind可以是一個很簡單的話題,因為它其實不過就是一個泛型的函數指針。但是如果那么來談,就沒意思了,也犯不上寫這篇東西。在我看來,這個事情要講的話,就應該講透,講到回調(callback)、代理(delegate)、信號(signal)和消息傳遞(messaging)的層面,因為它確實是太重要了。這個話題不但與面向對象的核心思想密切相關,而且是面向對象兩大流派之間交鋒的中心。圍繞這個問題的思考和爭論,幾乎把20年來所有主流的編程平臺和編程語言都攪進來了。所以,如果詳盡鋪陳,這個話題直接可以寫一本書。

            寫書我當然沒那個水平,但這個題目確實一直想動一動。然而這個主題實在太大,我實在沒有精力把它完整的寫下來;這個主題也很深,特別是涉及到并發環境有關的話題,我的理解還非常膚淺,總覺得我認識的很多高手都比我更有資格寫這個話題。所以猶豫了很久,要不要現在寫,該怎么寫。最后我覺得,確實不能把一篇博客文章寫成一本20年面向對象技術史記,所以決定保留大的架構,但是對其中具體的技術細節點到為止。我不會去詳細地列舉代碼,分析對象的內存布局,畫示意圖,但是會把最重要的結論和觀點寫下來,說得好聽一點是提綱挈領,說的不好聽就是語焉不詳。但無論如何,我想這樣一篇東西,一是談談我對這個事情的看法,二是“拋磚引玉”,引來高手的關注,引出更深刻和完整的敘述。

            下面開始。

            0. 程序設計有一個范式(paradigm)問題。所謂范式,就是組織程序的基本思想,而這個基本思想,反映了程序設計者對程序的一個基本的哲學觀,也就是說,他認為程序的本質是什么,他認為一個大的程序是由什么組成的。而這,又跟他對于現實世界的看法有關。顯然,這樣的看法不可能有很多種。編程作為一門行業,獨立存在快60年了,但是所出現的范式不過三種——過程范式、函數范式、對象范式。其中函數范式與現實世界差距比較大,在這里不討論。而過程范式和對象范式可以視為對程序本質的兩種根本不同的看法,而且能夠分別在現實世界中找到相應的映射。
            過程范式認為,程序是由一個又一個過程經過順序、選擇和循環的結構組合而成。反映在現實世界,過程范式體現了勞動分工之前“全能人”的工作特點——所有的事情都能干,所有的資源都是我的,只不過得具體的事情得一步步地來做。
            對象范式則反映了勞動分工之后的團隊協作的工作特點——每個人各有所長,各司其職,有各自的私有資源,工件和信息在人們之間彼此傳遞,最后完成工作。因此,對象范式也就形成了自己對程序的看法——程序是由一組對象組成,這些對象各有所能,通過消息傳遞實現協作。

            對象范式與過程范式相比,有三個突出的優勢,第一,由于實現了邏輯上的分工,降低了大規模程序的開發難度。第二,靈活性更好——若干對象在一起,可以靈活組合,可以以不同的方式協作,完成不同的任務,也可以靈活的替換和升級。第三,對象范式更加適應圖形化、網絡化、消息驅動的現代計算環境。

            所以,較之于過程范式,對象范式,或者說“面向對象”,確實是更具優勢的編程范式。最近看到一些文章抨擊面向對象,說面向對象是胡扯,我認為要具體分析。對面向對象的一部分批評,是沖著主流的“面向對象”語言去的,這確實是有道理的,我在下面也會談到,而且會罵得更狠。而另一個批評的聲音,主要而來自STL之父Alex Stepanov,他說的當然有他的道理,不過要知道該牛人是前蘇聯莫斯科國立羅蒙諾索夫大學數學系博士,你只要翻翻前蘇聯的大學數學教材就知道了,能夠在莫大拿到數學博士的,根本就是披著人皮的外星高等智慧。而我們編寫地球上的程序,可能還是應該以地球人的觀點為主。

            1. 重復一遍對象范式的兩個基本觀念:
            程序是由對象組成的;
            對象之間互相發送消息,協作完成任務;

            請注意,這兩個觀念與后來我們熟知的面向對象三要素“封裝、繼承、多態”根本不在一個層面上,倒是與再后來的“組件、接口”神合。

            2. 世界上第一個面向對象語言是Simula-67,第二個面向對象語言是Smalltalk-71。Smalltalk受到了Simula-67的啟發,基本出發點相同,但也有重大的不同。先說相同之處,Simula和Smalltalk都秉承上述對象范式的兩個基本觀念,為了方便對象的構造,也都引入了類、繼承等概念。也就是說,類、繼承這些機制是為了實現對象范式原則而構造出來的第二位的、工具性的機制,那么為什么后來這些第二位的東西篡了主位,后面我會再來分析。而Simula和Smalltalk最重大的不同,就是Simula用方法調用的方式向對象發送消息,而Smalltalk構造了更靈活和更純粹的消息發送機制。

            具體的說,向一個Simula對象中發送消息,就是調用這個對象的一個方法,或者稱成員函數。那么你怎么知道能夠在這個對象上調用這個成員函數呢?或者說,你怎么知道能夠向這個對象發送某個消息呢?這就要求你必須確保這個對象具有合適的類型,也就是說,你得先知道哦這個對象是什么,才能向它發消息。而消息的實現方式被直接處理為成員函數調用,或虛函數調用。

            而Smalltalk在這一點上做了一個歷史性的跨越,它實現了一個與目標對象無關的消息發送機制,不管那個對象是誰,也不管它是不是能正確的處理一個消息,作為發送消息的對象來說,可以毫無顧忌地抓住一個對象就發消息過去。接到消息的對象,要嘗試理解這個消息,并最后調用自己的過程來處理消息。如果這個消息能被處理,那個對象自然會處理好,如果不能被處理,Smalltalk系統會向消息的發送者回傳一個doesNotUnderstand消息,予以通知。對象不用關心消息是如何傳遞給另一個對象的,傳遞過程被分離出來(而不是像Simula那樣明確地被以成員函數調用的方式實現),可以是在內存中復制,也可以是進程間通訊。到了Smalltalk-80時,消息傳遞甚至可以跨越網絡。

            為了方便后面的討論,不妨把源自Simula的消息機制稱為“靜態消息機制”,把源自Smalltalk的消息機制稱為“動態消息機制”。

            Simula與Smalltalk之間對于消息機制的不同選擇,主要是因為兩者于用途。前者是用于仿真程序開發,而后者用于圖形界面環境構建,看上去各自合情合理。然而,就是這么一點簡單的區別,卻造成了巨大的歷史后果。

            3. 到了1980年代,C++出現了。Bjarne Stroustrup在博士期間深入研究過Simula,非常欣賞其思想,于是就在C語言語法的基礎之上,幾乎把Simula的思想照搬過來,形成了最初的C++。C++問世以之初,主要用于解決規模稍大的傳統類型的編程問題,迅速取得了巨大的成功,也證明了對象范式本身所具有的威力。

            大約在同期,Brad Cox根據Smalltalk的思想設計了Objective-C,可是由于其語法怪異,沒有流行起來。只有Steve Jobs這種具有禪宗美學鑒賞力的世外高人,把它奉為瑰寶,與1988年連鍋把Objective-C的團隊和產品一口氣買了下來。

            4. 就在同一時期,GUI成為熱門。雖然GUI的本質是對象范型的,但是當時(1980年代中期)的面向對象語言,包括C++語言,還遠不成熟,因此最初的GUI系統無一例外是使用C和匯編語言開發的。或者說,最初的GUI開發者硬是用抽象級別更低的語言構造了一個面向對象系統。熟悉Win32 SDK開發的人,應該知道我在說什么。

            5. 當時很多人以為,如果C++更成熟些,直接用C++來構造Windows系統會大大地容易。也有人覺得,盡管Windows系統本身使用C寫的,但是其面向對象的本質與C++更契合,所以在其基礎上包裝一個C++的GUI framework一定是輕而易舉。可是一動手人們就發現,完全不是那么回事。用C++開發Windows框架難得要死。為什么呢?主要就是Windows系統中的消息機制實際上是動態的,與C++的靜態消息機制根本配合不到一起去。在Windows里,你可以向任何一個窗口發送消息,這個窗口自己會在自己的wndproc里來處理這個消息,如果它處理不了,就交給default window/dialog proc去處理。而在C++里,你要向一個窗口發消息,就得確保這個窗口能處理這個消息,或者說,具有合適的類型。這樣一來的話,就會導致一個錯綜復雜的窗口類層次結構,無法實現。而如果你要讓所有的窗口類都能處理所有可能的消息,且不論這樣在邏輯上就行不通(用戶定義的消息怎么處理?),單在實現上就不可接受——為一個小小的不同就得創造一個新的窗口類,每一個小小的窗口類都要背上一個多達數百項的v-table,而其中可能99%的項都是浪費,不要說在當時,就是在今天,內存數量非常豐富的時候,如果每一個GUI程序都這么搞,用戶也吃不消。

            6. 實際上C++的靜態消息機制還引起了更深嚴重的問題——扭曲了人們對面向對象的理解。既然必須要先知道對象的類型,才能向對象發消息,那么“類”這個概念就特別重要了,而對象只不過是類這個模子里造出來的東西,反而不重要。漸漸的,“面向對象編程”變成了“面向類編程”,“面向類編程”變成了“構造類繼承樹”。放在眼前的鮮活的對象活動不重要了,反而是其背后的靜態類型系統成為關鍵。“封裝、繼承”這些第二等的特性,喧賓奪主,儼然成了面向對象的要素。每個程序員似乎都要先成為領域專家,然后成為領域分類學專家,然后構造一個完整的繼承樹,然后才能new出對象,讓程序跑起來。正是因為這個過程太漫長,太困難,再加上C++本身的復雜度就很大,所以C++出現這么多年,真正堪稱經典的面向對象類庫和框架,幾乎屈指可數。很多流行的庫,比如MFC、iostream,都暴露出不少問題。一般程序員總覺得是自己的水平不夠,于是下更大功夫去練劍。殊不知根本上是方向錯了,脫離了對象范式的本質,企圖用靜態分類法來對現實世界建模,去刻畫變化萬千的動態世界。這么難的事,你水平再高也很難做好。

            可以從一個具體的例子來理解這個道理,比如在一個GUI系統里,一個 Push Button 的設計問題。事實上在一個實際的程序里,一個 push button 到底“是不是”一個 button,進而是不是一個 window/widget,并不重要,本質上我根本不關心它是什么,它從屬于哪一個類,在繼承樹里處于什么位置,只要那里有這么一個東西,我可以點它,點完了可以發生相應的效果,就可以了。可是Simula –> C++ 所鼓勵的面向對象設計風格,非要上來就想清楚,a Push Button is-a Button, a Button is-a Command-Target Control, a Command-Target Control is-a Control, a Control is-a Window. 把這一圈都想透徹之后,才能 new 一個 Push Button,然后才能讓它工作。這就形而上學了,這就脫離實際了。所以很難做好。你看到 MFC 的類繼承樹,覺得設計者太牛了,能把這些層次概念都想清楚,自己的水平還不夠,還得修煉。實際上呢,這個設計是經過數不清的失敗和錢磨出來、砸出來的,MFC的前身 Afx 不是就失敗了嗎?1995年還有一個叫做 Taligent 的大項目,召集了包括 Eric Gamma 在內的一大堆牛人,要用C++做一個一統天下的application framework,最后也以慘敗告終,連公司都倒閉了,CEO車禍身亡,牛人們悉數遣散。附帶說一下,這個Taligent項目是為了跟NextSTEP和Microsoft Cairo競爭,前者用Objective-C編寫,后來發展為Cocoa,后者用傳統的Win32 + COM作為基礎架構,后來發展為Windows NT。而Objective-C和COM,恰恰就在動態消息分派方面,與C++迥然不同。后面還會談到。

            客觀地說,“面向類的設計”并不是沒有意義。來源于實踐又高于實踐的抽象和概念,往往能更有力地把握住現實世界的本質,比如MVC架構,就是這樣的有力的抽象。但是這種抽象,應該是來源于長期最佳實踐的總結和提高,而不是面對問題時主要的解決思路。過于強調這種抽象,無異于假定程序員各個都是哲學家,具有對現實世界準確而深刻的抽象能力,當然是不符合實際情況的。結果呢,剛學習面向對象沒幾天的程序員,對眼前鮮活的對象世界視而不見,一個個都煞有介事地去搞哲學冥想,企圖越過現實世界,去抽象出其背后本質,當然敗得很慘。

            其實C++問世之后不久,這個問題就暴露出來了。第一個C++編譯器 Cfront 1.0 是單繼承,而到了 Cfront 2.0,加入了多繼承。為什么?就是因為使用中人們發現邏輯上似乎完美的靜態單繼承關系,碰到復雜靈活的現實世界,就破綻百出——蝙蝠是鳥也是獸,水上飛機能飛也能游,它們該如何歸類呢?本來這應該促使大家反思繼承這個機制本身,但是那個時候全世界陷入繼承狂熱,于是就開始給繼承打補丁,加入多繼承,進而加入虛繼承,。到了虛繼承,明眼人一看便知,這只是一個語法補丁,是為了逃避職責而制造的一塊無用的遮羞布,它已經完全已經脫離實踐了——有誰在事前能夠判斷是否應該對基類進行虛繼承呢?

            到了1990年代中期,問題已經十分明顯。UML中有一個對象活動圖,其描述的就是運行時對象之間相互傳遞消息的模型。1994年Robert C. Martin在《Object-Oriented C++ Design Using Booch Method》中,曾建議面向對象設計從對象活動圖入手,而不是從類圖入手。而1995年出版的經典作品《Design Patterns》中,建議優先考慮組合而不是繼承,這也是盡人皆知的事情。這些跡象表明,在那個時候,面向對象社區里的思想領袖們,已經意識到“面向類的設計”并不好用。只可惜他們的革命精神還不夠。

            7. 你可能要問,Java 和.NET也是用繼承關系組織類庫,并進行設計的啊,怎么那么成功呢?這里有三點應該注意。第一,C++的難不僅僅在于其靜態結構體系,還有很多源于語言設計上的包袱,比如對C的兼容,比如沒有垃圾收集機制,比如對效率的強調,等等。一旦把這些包袱丟掉,設計的難度確實可以大大下降。第二,Java和.NET的核心類庫是在C++十幾年成功和失敗的經驗教訓基礎之上,結合COM體系優點設計實現的,自然要好上一大塊。事實上,在Java和.NET核心類庫的設計中很多地方,體現的是基于接口的設計,和真正的基于對象的設計。有了這兩個主角站臺,“面向類的設計”不能喧賓奪主,也能發揮一些好的作用。第三,如后文指出,Java和.NET中分別對C++最大的問題——缺少對象級別的delegate機制做出了自己的回應,這就大大彌補了原來的問題。

            盡管如此,Java還是沾染上了“面向類設計”的癌癥,基礎類庫里就有很多架床疊屋的設計,而J2EE/Java EE當中,這種形而上學的設計也很普遍,所以也引發了好幾次輕量化的運動。這方面我并不是太懂,可能需要真正的Java高手出來現身說法。我對Java的看法以前就講過——平臺和語言核心非常好,但風氣不好,崇尚華麗繁復的設計,裝牛逼的人太多。

            至于.NET,我聽陳榕介紹過,在設計.NET的時候,微軟內部對于是否允許繼承爆發了非常激烈的爭論。很多資深高人都強烈反對繼承。至于最后引入繼承,很大程度上是營銷需要壓倒了技術理性。盡管如此,由于有COM的基礎,又實現了非常徹底的delegate,所以 .NET 的設計水平還是很高的。它的主要問題不在這,在于太急于求勝,更新速度太快,基礎不牢。當然,根本問題還是微軟沒有能夠在Web和Mobile領域里占到多大的優勢,也就使得.NET沒有用武之地。

            8. COM。COM的要義是,軟件是由COM Components組成,components之間彼此通過接口相互通訊。這是否讓你回想起本文開篇所提出的對象范型的兩個基本原則?有趣的是,在COM的術語里,“COM Component ” 與“object ”通假,這就使COM的心思昭然若揭了。Don Box在Essential COM里開篇就說,COM是更好的C++,事實上就是告訴大家,形而上學的“面向類設計”不好使,還是回到對象吧。

            用COM開發的時候,一個組件“是什么”不重要,它具有什么接口,也就是說,能夠對它發什么消息,才是重要的。你可以用IUnknown::QueryInterface問組件能對哪一組消息作出反應。向組件分派消息也不一定要被綁定在方法調用上,如果實現了 IDispatch,還可以實現“自動化”調用,也就是COM術語里的 Automation,而通過 列集(mashal),可以跨進程、跨網絡向另一組件發送消息,通過 moniker,可以在分布式系統里定位和發現組件。如果你抱著“對象——消息”的觀念去看COM的設計,就會意識到,整個COM體系就是用規范如何做對象,如何發消息的。或者更直白一點,COM就是用C/C++硬是模擬出一個Smalltalk。而且COM的概念世界里沒有繼承,就其純潔性而言,比Smalltalk還Smalltalk。在對象泛型上,COM達到了一個高峰,領先于那個時代,甚至于比它的繼任.NET還要純潔。

            COM的主要問題是它的學習難度和安全問題,而且,它過于追求純潔性,完全放棄了“面向類設計” 的機制,顯得有點過。

            9. 好像有點扯遠了,其實還是在說正事。上面說到由于C++的靜態消息機制,導致了形而上學的“面向類的設計”,禍害無窮。但實際上,C++是有一個補救機會的,那就是實現對象級別的delegate機制。學過.NET的人,一聽delegate這個詞就知道是什么意思,但Java里沒有對應機制。在C++的術語體系里,所謂對象級別delegate,就是一個對象回調機制。通過delegate,一個對象A可以把一個特定工作,比如處理用戶的鼠標事件,委托給另一個對象B的一個方法來完成。A不必知道B的名字,也不用知道它的類型,甚至都不需要知道B的存在,只要求B對象具有一個簽名正確的方法,就可以通過delegate把工作交給B的這個方法來執行。在C語言里,這個機制是通過函數指針實現的,所以很自然的,在C++里,我們希望通過指向成員函數的指針來解決類似問題。

            然而就在這個問題上,C++讓人扼腕痛惜。

            posted on 2011-01-20 22:24 zhaoyg 閱讀(481) 評論(0)  編輯 收藏 引用 所屬分類: C/C++學習筆記
            精品一二三区久久aaa片| 久久精品人人做人人爽97 | 亚洲&#228;v永久无码精品天堂久久| 99久久精品免费观看国产| 思思久久好好热精品国产| 97久久综合精品久久久综合| 精品多毛少妇人妻AV免费久久 | 欧美丰满熟妇BBB久久久| 国产精品久久波多野结衣| 亚洲精品成人网久久久久久| 国内精品伊人久久久久AV影院| 亚洲国产成人久久综合一区77| 国产精品久久久久久久久免费| 怡红院日本一道日本久久 | 欧美伊人久久大香线蕉综合 | 久久亚洲日韩精品一区二区三区| 久久99国产精一区二区三区| 久久99九九国产免费看小说| 91精品国产高清久久久久久91| 色欲综合久久中文字幕网| 日批日出水久久亚洲精品tv| yellow中文字幕久久网| 蜜臀av性久久久久蜜臀aⅴ麻豆| 色天使久久综合网天天 | 欧美久久亚洲精品| 91超碰碰碰碰久久久久久综合| 99久久久精品免费观看国产| 2021最新久久久视精品爱| 性欧美大战久久久久久久| 久久亚洲2019中文字幕| 日韩AV毛片精品久久久| 久久亚洲2019中文字幕| 亚洲日韩欧美一区久久久久我| 久久影视综合亚洲| 亚洲国产成人久久综合野外| 国产精品久久久久久久app | 精品熟女少妇av免费久久| 亚洲人成网亚洲欧洲无码久久| 国产aⅴ激情无码久久| 久久人人爽人人爽人人片AV不| 亚洲综合伊人久久大杳蕉|