• <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>

            CG@CPPBLOG

            /*=========================================*/
            隨筆 - 76, 文章 - 39, 評(píng)論 - 137, 引用 - 0
            數(shù)據(jù)加載中……

            (ZT)C++批判

            C++批判

            以下文章翻譯自Ian Joyner所著的
            《C++?? A Critique of C++ and Programming and Language Trends of the 1990s》 3/E【Ian Joyner 1996】

            該篇文章已經(jīng)包含在Ian Joyner所寫的《Objects Unencapsulated 》一書中(目前已經(jīng)有了日文的翻譯版本),該書的介紹可參見(jiàn)于:
            http://www.prenhall.com/allbooks/ptr_0130142697.html
            http://efsa.sourceforge.net/cgi-bin/view/Main/ObjectsUnencapsulated
            http://www.accu.org/bookreviews/public/reviews/o/o002284.htm


            虛擬函數(shù)

              在所有對(duì)C++的批評(píng)中,虛擬函數(shù)這一部分是最復(fù)雜的。這主要是由于C++中復(fù)雜的機(jī)制所引起的。雖然本篇文章認(rèn)為多態(tài)(polymorphism)是實(shí)現(xiàn)面向?qū)ο缶幊蹋∣OP)的關(guān)鍵特性,但還是請(qǐng)你不要對(duì)此觀點(diǎn)(即虛擬函數(shù)機(jī)制是C++中的一大敗筆)感到有什么不安,繼續(xù)看下去,如果你僅僅想知道一個(gè)大概的話,那么你也可以跳過(guò)此節(jié)。【譯者注:建議大家還是看看這節(jié)會(huì)比較好】

             在C++中,當(dāng)子類改寫/重定義(override/redefine)了在父類中定義了的函數(shù)時(shí),關(guān)鍵字virtual使得該函數(shù)具有了多態(tài)性,但是 virtual關(guān)鍵字也并不是必不可少的(只要在父類中被定義一次就行了)。編譯器通過(guò)產(chǎn)生動(dòng)態(tài)分配(dynamic dispatch)的方式來(lái)實(shí)現(xiàn)真正的多態(tài)函數(shù)調(diào)用。

              這樣,在C++中,問(wèn)題就產(chǎn)生了:如果設(shè)計(jì)父類的人員不能預(yù)見(jiàn)到子類可能會(huì)改寫哪個(gè)函數(shù),那么子類就不能使得這個(gè)函數(shù)具有多態(tài)性。這對(duì)于C++來(lái)說(shuō)是一個(gè)很嚴(yán)重的缺陷,因?yàn)樗鼫p少了軟件組件(software components)的彈性(flexibility),從而使得寫出可重用及可擴(kuò)展的函數(shù)庫(kù)也變得困難起來(lái)。

             C++同時(shí)也允許函數(shù)的重載(overload),在這種情況下,編譯器通過(guò)傳入的參數(shù)來(lái)進(jìn)行正確的函數(shù)調(diào)用。在函數(shù)調(diào)用時(shí)所引用的實(shí)參類型必須吻合被重載的函數(shù)組(overloaded functions)中某一個(gè)函數(shù)的形參類型。重載函數(shù)與重寫函數(shù)(具有多態(tài)性的函數(shù))的不同之處在于:重載函數(shù)的調(diào)用是在編譯期間就被決定了,而重寫函數(shù)的調(diào)用則是在運(yùn)行期間被決定的。

             當(dāng)一個(gè)父類被設(shè)計(jì)出來(lái)時(shí),程序員只能猜測(cè)子類可能會(huì)重載/重寫哪個(gè)函數(shù)。子類可以隨時(shí)重載任何一個(gè)函數(shù),但這種機(jī)制并不是多態(tài)。為了實(shí)現(xiàn)多態(tài),設(shè)計(jì)父類的程序員必須指定一個(gè)函數(shù)為virtual,這樣會(huì)告訴編譯器在類的跳轉(zhuǎn)表(class jump table)【譯者竊以為是vtable,即虛擬函數(shù)入口表】中建立一個(gè)分發(fā)入口。于是,對(duì)于決定什么事情是由編譯器自動(dòng)完成,或是由其他語(yǔ)言的編譯器自動(dòng)完成這個(gè)重任就放到了程序員的肩上。這些都是從最初的C++的實(shí)現(xiàn)中繼承下來(lái)的,而和一些特定的編譯器及聯(lián)結(jié)器無(wú)關(guān)。

             對(duì)于重寫,我們有著三種不同的選擇,分別對(duì)應(yīng)于:“千萬(wàn)別”,“可以”及“一定要”重寫:

             1、重寫一個(gè)函數(shù)是被禁止的。子類必須使用已有的函數(shù)。
             2、函數(shù)可以被重寫。子類可以使用已有的函數(shù),也可以使用自己寫的函數(shù),前提是這個(gè)函數(shù)必須遵循最初的界面定義,而且實(shí)現(xiàn)的功能盡可能的少及完善。
             3、函數(shù)是一個(gè)抽象的函數(shù)。對(duì)于該函數(shù)沒(méi)有提供任何的實(shí)現(xiàn),每個(gè)子類都必須提供其各自的實(shí)現(xiàn)。
             
             父類的設(shè)計(jì)者必須要決定1和3中的函數(shù),而子類的設(shè)計(jì)者只需要考慮2就行了。對(duì)于這些選擇,程序語(yǔ)言必須要提供直接的語(yǔ)法支持。
             
            選項(xiàng)1:
             
             C ++并不能禁止在子類中重寫一個(gè)函數(shù)。即使是被聲明為private virtual的函數(shù)也可以被重寫。【Sakkinen92】中指出了即使在通過(guò)其他方法都不能訪問(wèn)到private virtual函數(shù),子類也可以對(duì)其進(jìn)行重寫。

            如下所示,將輸出:class B

            #include <stdio.h>

            class A
            {
            private:
                virtual void f()
                {
                    printf("class A\n");
                }

            public:
                void call_f()
                {
                    f();
                }
            };

            class B : public A
            {
            public:
                void f()
                {
                    printf("class B\n");
                }
            };


            int main()
            {
                B b;
                A* a = &b;

                a->call_f();

                return 0;
            }

            實(shí)現(xiàn)這種選擇的唯一方法就是不要使用虛擬函數(shù),但是這樣的話,函數(shù)就等于整個(gè)被替換掉了。首先,函數(shù)可能會(huì)在無(wú)意中被子類的函數(shù)給替換掉。在同一個(gè)scope中重新宣告一個(gè)函數(shù)將會(huì)導(dǎo)致名字沖突(name clash);編譯器將會(huì)就此報(bào)告出一個(gè)“duplicate declaration”的語(yǔ)法錯(cuò)誤。允許兩個(gè)擁有同名的實(shí)體存在于同一個(gè)scope中將會(huì)導(dǎo)致語(yǔ)義的二義性(ambiguity)及其他問(wèn)題(可參見(jiàn)于 name overloading這節(jié))。
             
             下面的例子闡明了第二個(gè)問(wèn)題:

             class A
             {
              public:
                   void nonvirt();
                   virtual void virt();
             };

             class B : public A
             {
              public:
                   void nonvirt();
                   void virt();
             };
             
             A a;
             B b;
             A *ap = &b;
             B *bp = &b;
             
             bp->nonvirt(); file://calls B::nonvirt as you would eXPect
             ap->nonvirt(); file://calls A::nonvirt even though this object is of type B
             ap->virt();  file://calls B::virt, the correct version of the routine for B objects
             
            在這個(gè)例子里,B擴(kuò)展或替換掉了A中的函數(shù)。B::nonvirt是應(yīng)該被B的對(duì)象調(diào)用的函數(shù)。在此處我們必須指出,C++給客戶端程序員(即使用我們這套繼承體系架構(gòu)的程序員)足夠的彈性來(lái)調(diào)用A::nonvirt或是B::nonvirt,但我們也可以提供一種更簡(jiǎn)單,更直接的方式:提供給A:: nonvirt和B::nonvirt不同的名字。這可以使得程序員能夠正確地,顯式地調(diào)用想要c調(diào)用的函數(shù),而

            不是陷入了上面的那種晦澀的,容易導(dǎo)致錯(cuò)誤的陷阱中去。

            具體方法如下:

             class B:  public A
             {
              public:
                   void b_nonvirt();
                   void virt();
             }

             B b;
             B *bp = &b;

            bp->nonvirt();  file://calls A::nonvirt
            bp->b_nonvirt(); file://calls B::b_nonvirt
             
              現(xiàn)在,B的設(shè)計(jì)者就可以直接的操縱B的接口了。程序要求B的客戶端(即調(diào)用B的代碼)能夠同時(shí)調(diào)用A::nonvirt和B::nonvirt,這點(diǎn)我們也做到了。就Object-Oriented Design(OOD)來(lái)說(shuō),這是一個(gè)不錯(cuò)的做法,因?yàn)樗峁┝私训慕涌诙x(strongly defined interface)【譯者認(rèn)為:即不會(huì)引起調(diào)用歧義的接口】。C++允許客戶端程序員在類的接口處賣弄他們的技巧,借以對(duì)類進(jìn)行擴(kuò)展。在上例中所出現(xiàn)的就是設(shè)計(jì)B的程序員不能阻止其他程序員調(diào)用A::nonvirt。類B的對(duì)象擁有它們自己的nonvirt,但是即便如此,B的設(shè)計(jì)者也不能保證通過(guò)B的接口就一定能調(diào)用到正確版本的nonvirt。
             
             C++同樣不能阻止系統(tǒng)中對(duì)其他處的改動(dòng)不會(huì)影響到B。假設(shè)我們需要寫一個(gè)類C,在C 中我們要求nonvirt是一個(gè)虛擬的函數(shù)。于是我們就必須回到A中將nonvirt改為虛擬的。但這又將使得我們對(duì)于B::nonvirt所玩弄的技巧又失去了作用(想想看,為什么:D)。對(duì)于C需要一個(gè)virtual的需求(將已有的nonvirtual改為virtual)使得我們改變了父類,這又使得所有從父類繼承下來(lái)的子類也相應(yīng)地有了改變。這已經(jīng)違背了OOP擁有低耦合的類的理由,新的需求,改動(dòng)應(yīng)該只產(chǎn)生局部的影響,而不是改變系統(tǒng)中其他地方,從而潛在地破壞了系統(tǒng)的已有部分。
             
             另一個(gè)問(wèn)題是,同樣的一條語(yǔ)句必須一直保持著同樣的語(yǔ)義。例如:對(duì)于諸如a->f()這樣的多態(tài)性語(yǔ)句的解釋,系統(tǒng)調(diào)用的是由最符合a所真正指向類型的那個(gè)f(),而不管對(duì)象的類型到底是A,還是A的子類。然而,對(duì)于C++的程序員來(lái)說(shuō),他們必須要清楚地了解當(dāng)f()被定義成virtual或是 non-virtual時(shí),a->f()的真正涵義。所以,語(yǔ)句a->f()不能獨(dú)立于其實(shí)現(xiàn),而且隱藏的實(shí)現(xiàn)原理也不是一成不變的。對(duì)于f ()聲明的一次改變將會(huì)相應(yīng)地改變調(diào)用它時(shí)的語(yǔ)義。與實(shí)現(xiàn)獨(dú)立意味著對(duì)于實(shí)現(xiàn)的改變不會(huì)改變語(yǔ)句的語(yǔ)義,或是執(zhí)行的語(yǔ)義。
             
             如果在聲明中的改變導(dǎo)致相應(yīng)的語(yǔ)義改變,編譯器應(yīng)該能檢測(cè)到錯(cuò)誤的產(chǎn)生。程序員應(yīng)該在聲明被改變的情況下保持語(yǔ)義的不變。這反映了軟件開(kāi)發(fā)中的動(dòng)態(tài)特性,在其中你將能發(fā)現(xiàn)程序文本的永久改變。
             
             其他另一個(gè)與a->f()相應(yīng)的,語(yǔ)義不能被保持不變的例子是:構(gòu)造函數(shù)(可參考于C++ ARM, section 10.9c, p 232)。而Eiffel和Java則不存在這樣的問(wèn)題。它們中所采用的機(jī)制簡(jiǎn)單而又清晰,不會(huì)導(dǎo)致C++中所產(chǎn)生的那些令人吃驚的現(xiàn)象。在Java中,所有的方法都是虛擬的,為了讓一個(gè)方法【譯者注:對(duì)應(yīng)于C++的函數(shù)】不能被重寫,我們可以用final修飾符來(lái)修飾這個(gè)方法。
             
             Eiffel允許程序員指定一個(gè)函數(shù)為frozen,在這種情況下,這個(gè)函數(shù)就不能在子類中被重寫。
             
            選項(xiàng)2:

              是使用現(xiàn)有的函數(shù)還是重寫一個(gè),這應(yīng)該是由撰寫子類的程序員所決定的。在C++中,要想擁有這種能力則必須在父類中指定為virtual。對(duì)于OOD來(lái)說(shuō),你所決定不想作的與你所決定想作的同樣重要,你的決定應(yīng)該是越遲下越好。這種策略可以避免錯(cuò)誤在系統(tǒng)前期就被包含進(jìn)去。你作決定越早,你就越有可能被以后所證明是錯(cuò)誤的假設(shè)所包圍;或是你所作的假設(shè)在一種情況下是正確的,然而在另一種情況下卻會(huì)出錯(cuò),從而使得你所寫出來(lái)的軟件比較脆弱,不具有重用性(reusable)【譯者注:軟件的可重用性對(duì)于軟件來(lái)說(shuō)是一個(gè)很重要的特性,具體可以參考
            《Object-Oriented Software Construct》中對(duì)于軟件的外部特性的敘述,P7, Reusability, Charpter 1.2 A REVIEW OF EXTERNAL FACTORS】。
             
             C ++要求我們?cè)诟割愔芯鸵付赡艿亩鄳B(tài)性(這可以通過(guò)virtual來(lái)指定),當(dāng)然我們也可以在繼承鏈中的中間的類導(dǎo)入virtual機(jī)制,從而預(yù)先判斷某個(gè)函數(shù)是否可以在子類中被重定義。

            這種做法將導(dǎo)致問(wèn)題的出現(xiàn):如那些并非真正多態(tài)的函數(shù)(not actually polymorphic)也必須通過(guò)效率較低的table技術(shù)來(lái)被調(diào)用,而不像直接調(diào)用那個(gè)函數(shù)來(lái)的高效【譯者注:在文章的上下文中并沒(méi)有出現(xiàn)not actually polymorphic特性的確切定義,根據(jù)我的理解,應(yīng)該是聲明為polymorphic,而實(shí)際上的動(dòng)作并沒(méi)能體現(xiàn)polymorphic這樣的一種特性】。雖然這樣做并不會(huì)引起大量的花費(fèi)(overhead),但我們知道,在OO程序中經(jīng)常會(huì)出現(xiàn)使用大量的、短小的、目標(biāo)單一明確的函數(shù),如果將所有這些都累計(jì)下來(lái),也會(huì)導(dǎo)致一個(gè)相當(dāng)可觀的花費(fèi)。C++中的

            政策是這樣的:需要被重定義的函數(shù)必須被聲明為virtual。糟糕的是,C++同時(shí)也說(shuō)了, non-virtual函數(shù)不能被重定義,這使得設(shè)計(jì)使用子類的程序員就無(wú)法對(duì)于這些函數(shù)擁有自己的控制權(quán)。【譯者注:原作中此句顯得有待推敲,原文是這樣寫的:it says that non-virtual routines cannot be redefined, 我猜測(cè)作者想表達(dá)的意思應(yīng)該是:If you have defined a non-virtual routine in base, then it cannot be virtual in the base whether you redefined it as virtual in descendant.】

            Rumbaugh等人對(duì)于C++中的虛擬機(jī)制的批評(píng)如下:C++擁有了簡(jiǎn)單實(shí)現(xiàn)繼承及動(dòng)態(tài)方法調(diào)用的特性,但一個(gè)C++的數(shù)據(jù)結(jié)構(gòu)并不能自動(dòng)成為面向?qū)ο蟮摹7椒ㄕ{(diào)用決議(method resolution)以及在子類中重寫一個(gè)函數(shù)操作的前提必須是這個(gè)函數(shù)/方法已經(jīng)在父類中被聲明為virtual。也就是說(shuō),必須在最初的類中我們就能預(yù)見(jiàn)到一個(gè)函數(shù)是否需要被重寫。不幸的是,類的撰寫者可能不會(huì)預(yù)期到需要定義一個(gè)特殊的子類,也可能不會(huì)知道那些操作將要在子類中被重寫。這意味著當(dāng)子類被定義時(shí),我們經(jīng)常需要回過(guò)頭去修改我們的父類,并且使得對(duì)于通過(guò)創(chuàng)建子類來(lái)重用已有的庫(kù)的限制極為嚴(yán)格,尤其是當(dāng)這個(gè)庫(kù)的源代碼不能被獲得是更是如此。(當(dāng)然,你也可以將所有的操作都定義為virtual,并愿意為此付出一些小小的內(nèi)存花費(fèi)用于函數(shù)調(diào)用)【RBPEL91】
             
             然而,讓程序員來(lái)處理virtual是一個(gè)錯(cuò)誤的機(jī)制。編譯器應(yīng)該能夠檢測(cè)到多態(tài),并為此產(chǎn)生所必須的、潛在的實(shí)現(xiàn)virtual的代碼。讓程序員來(lái)決定 virtual與否對(duì)于程序員來(lái)說(shuō)是增加了一個(gè)簿記工作的負(fù)擔(dān)。這也就是為什么C++只能算是一種弱的面向?qū)ο笳Z(yǔ)言(weak object-oriented language):因?yàn)槌绦騿T必須時(shí)刻注意著一些底層的細(xì)節(jié)(low level details),而這些本來(lái)可以由編譯器自動(dòng)處理的。
             
             在C++中的另一個(gè)問(wèn)題是錯(cuò)誤的重寫(mistaken overriding),父類中的函數(shù)可以在毫不知情的情況下被重寫。編譯器應(yīng)該對(duì)于同一個(gè)名字空間中的重定義報(bào)錯(cuò),除非編寫子類的程序員指出他是有意這么做的(即對(duì)于虛函數(shù)的重寫)。我們可以使用同一個(gè)名字,但是程序員必須清楚自己在干什么,并且顯式地聲明它,尤其是在將自己的程序與已經(jīng)存在的程序組件組裝成新的系統(tǒng)的情況下更要如此。除非程序員顯式地重寫已有的虛函數(shù),否則編譯器必須要給我們報(bào)告出現(xiàn)了名字被聲明多處(duplicate declaration)的錯(cuò)誤。然而,C++卻采用了Simula最初的做法,而這種方法到現(xiàn)在已經(jīng)得到了改良。其他的一些程序語(yǔ)言通過(guò)采用了更好的、更加顯式的方法,避免了錯(cuò)誤重定義的出現(xiàn)。
             
             解決方法就是virtual不應(yīng)該在父類中就被指定好。當(dāng)我們需要運(yùn)行時(shí)的動(dòng)態(tài)綁定時(shí),我們就在子類中指定需要對(duì)某個(gè)函數(shù)進(jìn)行重寫。這樣做的好處在于:對(duì)于具有多態(tài)性的函數(shù),編譯器可以檢測(cè)其函數(shù)簽名(function signature)的一致性;而對(duì)于重載的函數(shù),其函數(shù)簽名在某些方面本來(lái)就不一樣。第二個(gè)好處表現(xiàn)在,在程序的維護(hù)階段,能夠清楚地表達(dá)程序的最初意愿。而實(shí)際上后來(lái)的程序員卻經(jīng)常要猜測(cè)先前的程序員是不是犯了什么錯(cuò)誤,選擇一個(gè)相同的名字,還是他本來(lái)就想重載這個(gè)函數(shù)。
             
             在 Java中,沒(méi)有virtual這個(gè)關(guān)鍵字,所有的方法在底層都是多態(tài)的。當(dāng)方法被定義為static, private或是final時(shí),Java直接調(diào)用它們而不是通過(guò)動(dòng)態(tài)的查表的方式。這意味著在需要被動(dòng)態(tài)調(diào)用時(shí),它們卻是非多態(tài)性的函數(shù),Java的這種動(dòng)態(tài)特性使得編譯器難以進(jìn)行進(jìn)一步的優(yōu)化。
             
             Eiffel和Object Pascal迎合了這個(gè)選項(xiàng)。在它們中,編寫子類的程序員必須指定他們所想進(jìn)行的重定義動(dòng)作。我們可以從這種做法中得到巨大的好處:對(duì)于以后將要閱讀這些程序的人及程序的將來(lái)維護(hù)者來(lái)說(shuō),可以很容易地找出來(lái)被重寫的函數(shù)。因而選項(xiàng)2最好是在子類中被實(shí)現(xiàn)。
             
             Eiffel和Object Pascal都優(yōu)化了函數(shù)調(diào)用的方式:因?yàn)樗麄冎恍枰a(chǎn)生那些真正多態(tài)的函數(shù)的調(diào)用分配表的入口項(xiàng)。對(duì)于怎樣做,我們將會(huì)在global analysis這節(jié)中討論。
             
            選項(xiàng)3:

              純虛函數(shù)這樣的做法迎合了讓一個(gè)函數(shù)成為抽象的,從而子類在實(shí)例化時(shí)必須為其提供一個(gè)實(shí)現(xiàn)這樣的一個(gè)條件。沒(méi)有重寫這些函數(shù)的任何子類同樣也是抽象類。這個(gè)概念沒(méi)有錯(cuò),但是請(qǐng)你看一看pure virtual functions這一節(jié),我們將在那節(jié)中對(duì)于這種術(shù)語(yǔ)及語(yǔ)法進(jìn)行批判討論。
             
             Java也擁有純虛方法(同樣Eiffel也有),實(shí)現(xiàn)方法是為該方法加上deffered標(biāo)注。
             
            結(jié)論:

             virtual 的主要問(wèn)題在于,它強(qiáng)迫編寫父類的程序員必須要猜測(cè)函數(shù)在子類中是否有多態(tài)性。如果這個(gè)需求沒(méi)有被預(yù)見(jiàn)到,或是為了優(yōu)化、避免動(dòng)態(tài)調(diào)用而沒(méi)有被包含進(jìn)去的話,那么導(dǎo)致的可能性就是極大的封閉,勝過(guò)了開(kāi)放。在C++的實(shí)現(xiàn)中,virtual提高了重寫的耦合性,導(dǎo)致了一種容易產(chǎn)生錯(cuò)誤的聯(lián)合。

            Virtual是一種難以掌握的語(yǔ)法,相關(guān)的諸如多態(tài)、動(dòng)態(tài)綁定、重定義以及重寫等概念由于面向于問(wèn)題域本身,掌握起來(lái)就相對(duì)容易多了。虛擬函數(shù)的這種實(shí)現(xiàn)機(jī)制要求編譯器為其在class中建立起virtual table入口,而global analysis并不是由編譯器完成的,所以一切的重?fù)?dān)都?jí)涸诹顺绦騿T的肩上了。多態(tài)是目的,虛擬機(jī)制就是手段。Smalltalk, Objective-C, Java和Eiffel都是使用其他的一種不同的方法來(lái)實(shí)現(xiàn)多態(tài)的。
             
             Virtual是一個(gè)例子,展示了C ++在OOP的概念上的混沌不清。程序員必須了解一些底層的概念,甚至要超過(guò)了解那些高層次的面向?qū)ο蟮母拍睢irtual把優(yōu)化留給了程序員;其他的方法則是由編譯器來(lái)優(yōu)化函數(shù)的動(dòng)態(tài)調(diào)用,這樣做可以將那些不需要被動(dòng)態(tài)調(diào)用的分配(即不需要在動(dòng)態(tài)調(diào)用表中存在入口)100%地消除掉。對(duì)于底層機(jī)制,感興趣的應(yīng)該是那些理論家及編譯器實(shí)現(xiàn)者,一般的從業(yè)者則沒(méi)有必要去理解它們,或是通過(guò)使用它們來(lái)搞清楚高層的概念。在實(shí)踐中不得不使用它們是一件單調(diào)乏味的事情,并且還容易導(dǎo)致出錯(cuò),這阻止了軟件在底層技術(shù)及運(yùn)行機(jī)制下(參見(jiàn)并發(fā)程序)的更好適應(yīng),降低了軟件的彈性及可重用性。

            全局分析

             【P&S 94】中提到對(duì)于類型安全的檢測(cè)來(lái)說(shuō)有兩種假設(shè)。一種是封閉式環(huán)境下的假設(shè),此時(shí)程序中的各個(gè)部分在編譯期間就能被確定,然后我們可以對(duì)于整個(gè)程序來(lái)進(jìn)行類型檢測(cè)。另一種是開(kāi)放式環(huán)境下的假設(shè),此時(shí)對(duì)于類型的檢測(cè)是在單獨(dú)的模塊中進(jìn)行的。對(duì)于實(shí)際開(kāi)發(fā)和建立原型來(lái)說(shuō),第二種假設(shè)顯得十分有效。然而,【P&S 94】中又提到,“當(dāng)一種已經(jīng)完成的軟件產(chǎn)品到達(dá)了成熟期時(shí),采用封閉式環(huán)境下的假設(shè)就可以被考慮了,因?yàn)檫@樣可以使得一些比較高級(jí)的編譯技術(shù)得以有了用武之處。只有在整個(gè)程序都被了解的情況下,我們才可能在其上面執(zhí)行諸如全局寄存器分配、程序流程分析及無(wú)效代碼檢測(cè)等動(dòng)作。”(附:【P&S 94】Jens Palsberg and Michael I. Schwartzbach, Object-Oriented Type Systems, Wiley 1994)
             
             C++中的一個(gè)主要問(wèn)題就是:對(duì)于程序的分析過(guò)程被編譯器(工作于開(kāi)放式環(huán)境下的假設(shè))和鏈接器(依賴于十分有限的封閉式環(huán)境下的分析)給劃分開(kāi)了。封閉式環(huán)境下的或是全局的分析被采用的實(shí)質(zhì)原因有兩個(gè)方面:首先,它可以保證匯編系統(tǒng)的一致性;其次,它通過(guò)提供自動(dòng)優(yōu)化,減輕了程序員的負(fù)擔(dān)。
             
             程序員能夠被減輕的主要負(fù)擔(dān)是:設(shè)計(jì)父類的程序員不再需要(不得不)通過(guò)利用虛擬函數(shù)的修飾成份(virtual),來(lái)協(xié)助編譯器建立起vtable。正如我們?cè)?#8220;虛擬函數(shù)”中所說(shuō),這樣做將會(huì)影響到軟件的彈性。Vtable不應(yīng)該在一個(gè)單獨(dú)的類被編譯時(shí)就被建立起來(lái),最好是在整個(gè)系統(tǒng)被裝配在一起時(shí)一并被建立。在系統(tǒng)被裝配(鏈接)時(shí)期,編譯器和鏈接器協(xié)同起來(lái),就可以完全決定一個(gè)函數(shù)是否需要在vtable中占有一席之地。除上述之外,程序員還可以自由地使用在其他模塊中定義的一些在本地不可見(jiàn)的信息;并且程序員不再需要維護(hù)頭文件的存在了。
             
             在Eiffel和Object Pascal中,全局分析被應(yīng)用于整個(gè)系統(tǒng)中,決定真正的多態(tài)性的函數(shù)調(diào)用,并且構(gòu)造所需的vtable。在Eiffel中,這些是由編譯器完成的。在 Object Pascal中,Apple擴(kuò)展了鏈接器的功能,使之具有全局分析的能力。這樣的全局分析在C/Unix環(huán)境下很難被實(shí)現(xiàn),所以在C++中,它也沒(méi)有被包含進(jìn)去,使得負(fù)擔(dān)被留給了程序員。
             
             為了將這個(gè)負(fù)擔(dān)從程序員身上移除,我們應(yīng)該將全局分析的功能內(nèi)置于鏈接器中。然而,由于C++一開(kāi)始的版本是作為一個(gè)Cfront預(yù)處理器實(shí)現(xiàn)的,對(duì)于鏈接器所做的任何必要的改動(dòng)不能得到保證。C++的最初實(shí)現(xiàn)版本看起來(lái)就像一個(gè)拼湊起來(lái)的東西,到處充滿著漏洞。C++的設(shè)計(jì)嚴(yán)格地受限于其實(shí)現(xiàn)技術(shù),而不是其他(例如沒(méi)有采用好的程序語(yǔ)言設(shè)計(jì)原理等),因?yàn)槟菢泳托枰碌木幾g器和鏈接器了。也就是說(shuō),現(xiàn)在的C++發(fā)展嚴(yán)格地受限于其最初的試驗(yàn)性質(zhì)的產(chǎn)品。
             
             我現(xiàn)在確信這種技術(shù)上的依賴關(guān)系(即C++ 依賴于早先的C)嚴(yán)重地?fù)p害了C++,使之不是一個(gè)完整意義上的面向?qū)ο蟮母呒?jí)語(yǔ)言。一個(gè)高級(jí)語(yǔ)言可以將簿記工作從程序員身上接手過(guò)去,交給編譯器去完成,這也是高級(jí)語(yǔ)言的主要目的。缺乏全局(或是封閉式環(huán)境下的)分析是C++的一個(gè)主要不足,這使得C++在和Eiffel之類的語(yǔ)言相比時(shí)顯得十分地不足。由于Eiffel堅(jiān)持系統(tǒng)層次上的有效性及全局分析,這意味著Eiffel要比C++顯得有雄心多了,但這也是Eiffel產(chǎn)品為什么出現(xiàn)地這么緩慢的主要原因。
             
             Java只有在需要時(shí)才動(dòng)態(tài)地載入軟件的部分,并將它們鏈接起來(lái)成為一個(gè)可以運(yùn)行的系統(tǒng)。也因而使得靜態(tài)的編譯期間的全局分析變成不可能的了(因?yàn)镴ava被設(shè)計(jì)成為一個(gè)動(dòng)態(tài)的語(yǔ)言)。然而,Java假設(shè)所有的方法都是virtual的,這也就是為什么Java和 Eiffel是完全不同的工具的一個(gè)原因。關(guān)于Eiffel,可以參見(jiàn)于Dynamic Linking in Eiffel(DLE)。

            保證類型安全的聯(lián)結(jié)屬性(type-safe linkage)

             C ++ARM中解釋說(shuō)type-safe linkage并不能100%的保證類型安全。既然它不那100%的保證類型安全,那么它就肯定是不安全的。統(tǒng)計(jì)分析顯示:即便在很苛刻的情況下,C++ 出現(xiàn)單獨(dú)的O-ring錯(cuò)誤的可能性也只有0.3%。但我們一旦將6種這樣的可能導(dǎo)致出錯(cuò)的情況聯(lián)合起來(lái)放在一起,出錯(cuò)的幾率就變得大為可觀了。在軟件中,我們經(jīng)常能夠看到一些錯(cuò)誤的起因就是其怪異的聯(lián)合。OO的一個(gè)主要目的就是要減少這種奇怪的聯(lián)合出現(xiàn)。
             
             大多數(shù)問(wèn)題的起因都是一些難以察覺(jué)的錯(cuò)誤,而不是那些簡(jiǎn)單明了的錯(cuò)誤導(dǎo)致問(wèn)題的產(chǎn)生。而且在通常的情況下,不到真正的臨界時(shí)期,這樣的錯(cuò)誤一般都很難被檢測(cè)到,但我們不能由此就低估了這種情況的嚴(yán)肅性。有許多的計(jì)劃都依賴于其操作的正確性,如太空計(jì)劃、財(cái)政結(jié)算等。在這些計(jì)劃中采用不安全的解決方案是一種不負(fù)責(zé)任的做法,我們應(yīng)該嚴(yán)厲禁止類似情況的出現(xiàn)。
             
             C++在type-safe linkage上相對(duì)于C來(lái)說(shuō)有了巨大的進(jìn)步。在C中,鏈接器可以將一個(gè)帶有參數(shù)的諸如f(p1,...)這樣的函數(shù)鏈接到任意的函數(shù)f()上面,而這個(gè) f()甚至可以沒(méi)有參數(shù)或是帶有不同的參數(shù)都行。這將會(huì)導(dǎo)致程序在運(yùn)行時(shí)出錯(cuò)。由于C++的type-safe linkage機(jī)制是一種在鏈接器上實(shí)做的技巧,對(duì)于這樣的不一致性,C++將統(tǒng)統(tǒng)拒絕。
             
             C++ARM將這樣的情況概括如下--“處理所有的不一致性->這將使得C++得以100%的保證類型安全->這將要求對(duì)鏈接器的支持或是機(jī)制(環(huán)境)能夠允許編譯器訪問(wèn)在其他編譯單元里面的信息”。
             
              那么為什么市面上的C++編譯器(至少AT&T的是如此)不提供訪問(wèn)其他畢業(yè)單元中的信息的能力呢?為什么到現(xiàn)在也沒(méi)有一種特殊的專門為C++設(shè)計(jì)的鏈接器出現(xiàn),可以100%的保證類型安全呢?答案是C++缺乏一種全局分析的能力(在上一節(jié)中我們討論過(guò))。另外,在已有的程序組件外構(gòu)造我們的系統(tǒng)已經(jīng)是一種通用的Unix軟件開(kāi)發(fā)方式,這實(shí)現(xiàn)了一定的重用,然而它并不能為面向?qū)ο蠓绞降闹赜锰峁┱嬲膹椥约耙恢滦浴?/span>
             
             在將來(lái), Unix可能會(huì)被面向?qū)ο蟮牟僮飨到y(tǒng)給替代,這樣的操作系統(tǒng)足夠的“開(kāi)放”并且能夠被合適地裁剪用以符合我們的需求。通過(guò)使用管道(pipe)及標(biāo)志 (flag),Unix下的軟件組件可以被重復(fù)利用以提供所需的近似功能。這種方法在一定的情況下行之有效,并且頗負(fù)效率(如小型的內(nèi)部應(yīng)用,或是用以進(jìn)行快速原型研究),但對(duì)于大規(guī)模、昂貴的、或是對(duì)于安全性要求很高的應(yīng)用來(lái)說(shuō),采取這樣的開(kāi)發(fā)方法就不再適合了。在過(guò)去的十年中,集成的軟件(即不采用外部組件開(kāi)發(fā)的軟件)的優(yōu)點(diǎn)已經(jīng)得到了認(rèn)同。傳統(tǒng)的Unix系統(tǒng)不能提供這樣的優(yōu)點(diǎn)。相比而言,集成的系統(tǒng)更加的復(fù)雜,對(duì)于開(kāi)發(fā)它們的開(kāi)發(fā)人員有著更多的要求,但是最終用戶(end user)要求的就是這樣的軟件。將所有的東西拙劣的放置于一起構(gòu)成的系統(tǒng)是不可接受的。現(xiàn)在,軟件開(kāi)發(fā)的重心已經(jīng)轉(zhuǎn)到組件式軟件開(kāi)發(fā)上面來(lái)了,如公共領(lǐng)域的OpenDoc或是Microsoft的OLE。
             
             對(duì)于鏈接來(lái)說(shuō),更進(jìn)一步的問(wèn)題出現(xiàn)在:不同的編譯單元和鏈接系統(tǒng)可能會(huì)使用不同的名字編碼方式。這個(gè)問(wèn)題和type-safe linkage有關(guān),不過(guò)我們將會(huì)在“重用性及兼容性”這節(jié)講述之。
             
             Java使用了一種不同的動(dòng)態(tài)鏈接機(jī)制,這種機(jī)制被設(shè)計(jì)的很好,沒(méi)有使用到Unix的鏈接器。Eiffel則不依賴于Unix或是其他平臺(tái)上的鏈接器來(lái)檢測(cè)這些問(wèn)題,一切都由編譯器完成。
             
             Eiffel 定義了一種系統(tǒng)層上的有效性(system-level validity)。一個(gè)Eiffel編譯器也就因此需要進(jìn)行封閉環(huán)境下的分析,而不是依賴于鏈接器上的技巧。你也可以就此認(rèn)為Eiffel程序能夠保證 100%的類型安全。對(duì)于Eiffel來(lái)說(shuō)有一個(gè)缺點(diǎn)就是,編譯器需要干的事情太多了。(通常我們會(huì)說(shuō)的是它太“慢”了,但這不夠精確)目前我們可以通過(guò)對(duì)于Eiffel提供一定的擴(kuò)展來(lái)解決這個(gè)問(wèn)題,如融冰技術(shù)(melting-ice technology),它可以使得我們對(duì)于系統(tǒng)的改動(dòng)和測(cè)試可以在不需要每次都進(jìn)行重新編譯的情況下進(jìn)行。
             
             現(xiàn)在讓我們來(lái)概括一下前兩個(gè)小節(jié) - 有兩個(gè)原因使我們需要進(jìn)行全局(或封閉環(huán)境下的)分析:一致性檢測(cè)及優(yōu)化。這樣做可以減掉程序員身上大量的負(fù)擔(dān),而缺乏它是C++中的一個(gè)很大的不足。

            函數(shù)重載

             C++允許在參數(shù)類型不同的前提下重載函數(shù)。重載的函數(shù)與具有多態(tài)性的函數(shù)(即虛函數(shù))不同處在于:調(diào)用正確的被重載函數(shù)實(shí)體是在編譯期間就被決定了的;而對(duì)于具有多態(tài)性的函數(shù)來(lái)說(shuō),是通過(guò)運(yùn)行期間的動(dòng)態(tài)綁定來(lái)調(diào)用我們想調(diào)用的那個(gè)函數(shù)實(shí)體。多態(tài)性是通過(guò)重定義(或重寫)這種方式達(dá)成的。請(qǐng)不要被重載 (overloading)和重寫(overriding)所迷惑。重載是發(fā)生在兩個(gè)或者是更多的函數(shù)具有相同的名字的情況下。區(qū)分它們的辦法是通過(guò)檢測(cè)它們的參數(shù)個(gè)數(shù)或者類型來(lái)實(shí)現(xiàn)的。重載與CLOS中的多重分發(fā)(multiple dispatching)不同,對(duì)于參數(shù)的多重分發(fā)是在運(yùn)行期間多態(tài)完成的。
             
             【Reade 89】中指出了重載與多態(tài)之間的不同。重載意味著在相同的上下文中使用相同的名字代替出不同的函數(shù)實(shí)體(它們之間具有完全不同的定義和參數(shù)類型)。多態(tài)則只具有一個(gè)定義體,并且所有的類型都是由一種最基本的類型派生出的子類型。C. Strachey指出,多態(tài)是一種參數(shù)化的多態(tài),而重載則是一種特殊的多態(tài)。用以判斷不同的重載函數(shù)的機(jī)制就是函數(shù)標(biāo)示(function signature)。
             
             重載在下面的例子中顯得很有用:

             max( int, int )
             max( real, real )
             
              這將確保相對(duì)于類型int和real的最佳的max函數(shù)實(shí)體被調(diào)用。但是,面向?qū)ο蟮某绦蛟O(shè)計(jì)為該函數(shù)提供了一個(gè)變量,對(duì)象本身被被當(dāng)作一個(gè)隱藏的參數(shù)傳遞給了函數(shù)(在C++中,我們把它稱為this)。由于這樣,在面向?qū)ο蟮母拍钪杏蛛[式地包含了一種對(duì)等的但卻更有更多限制的形式。對(duì)于上述討論的一個(gè)簡(jiǎn)單例子如下:

             int i, j;
             real r, s;
             i.max(j);
             r.max(s);
             
             但如果我們這樣寫:i.max(r),或是r.max(j),編譯器將會(huì)告訴我們?cè)谶@其中存在著類型不匹配的錯(cuò)誤。當(dāng)然,通過(guò)重載運(yùn)算符的操作,這樣的行為是可以被更好地表達(dá)如下:

             i max j 或者 r max s

             但是,min和max都是特殊的函數(shù),它們可以接受兩個(gè)或者更多的同一類型的參數(shù),并且還可以作用在任意長(zhǎng)度的數(shù)組上。因此,在Eiffel中,對(duì)于這種情況最常見(jiàn)的代碼形式看起來(lái)就像這樣:

             il:COMPARABLE_LIST[INTEGER]
             rl:COMPARABLE_LIST[REAL]
             
             i := il.max
             r := rl.max
             
              上面的例子顯示,面向?qū)ο蟮木幊痰浞?paradigm),特別是和范型化(genericity)結(jié)合在一起時(shí),也可以達(dá)到函數(shù)重載的效果而不需要C+ +中的函數(shù)重載那樣的聲明形式。然而是C++使得這種概念更加一般化。C++這樣作的好處在于,我們可以通過(guò)不止一個(gè)的參數(shù)來(lái)達(dá)到重載的目的,而不是僅使用一個(gè)隱藏的當(dāng)前對(duì)象作為參數(shù)這樣的形式。
             
             另外一個(gè)我們需要考慮的因素是,決定(resolved)哪個(gè)重載函數(shù)被調(diào)用是在編譯階段完成的事情,但對(duì)于重寫來(lái)說(shuō)則推后到了運(yùn)行期間。這樣看起來(lái)好像重載能夠使我們獲得更多性能上的好處。然而,在全局分析的過(guò)程中編譯器可以檢測(cè)函數(shù)min 和max是否處在繼承的最末端,然后就可以直接的調(diào)用它們(如果是的話)。這也就是說(shuō),編譯器檢查到了對(duì)象i和r,然后分析對(duì)應(yīng)于它們的max函數(shù),發(fā)現(xiàn)在這種情況下沒(méi)有任何多態(tài)性被包含在內(nèi),于是就為上面的語(yǔ)句產(chǎn)生了直接調(diào)用max的目標(biāo)代碼。與此相反的是,如果對(duì)象n被定義為一個(gè)NUMBER, NUMBER又提供一個(gè)抽象的max函數(shù)聲明(我們所用的REAL.max和INTERGER.max都是從它繼承來(lái)的),那么編譯器將會(huì)為此產(chǎn)生動(dòng)態(tài)綁定的代碼。這是因?yàn)閚既可能是INTEGER,也有可能是REAL。
             
             現(xiàn)在你是不是覺(jué)得C++的這種方法(即通過(guò)提供不同的參數(shù)來(lái)實(shí)現(xiàn)函數(shù)的重載)很有用?不過(guò)你還必須明白,面向?qū)ο蟮某绦蛟O(shè)計(jì)對(duì)此有著種種的限制,存在著許多的規(guī)則。C++是通過(guò)指定參數(shù)必須與基類相符合的方式實(shí)現(xiàn)它的。傳入函數(shù)中的參數(shù)只能是基類,或是基類的派生類。

            例如:

             A.f( B someB )
             class B ...;
             class D : public B ...;
             A a;
             D d;
             a.f( d );

             其中d必須與類'B'相符,編譯器會(huì)檢測(cè)這些。
             
              通過(guò)不同的函數(shù)簽名(signature)來(lái)實(shí)現(xiàn)函數(shù)重載的另一種可行的方法是,給不同的函數(shù)以不同的名字,以此來(lái)使得它們的簽名不同。我們應(yīng)該使用名字來(lái)作為區(qū)分不同實(shí)體(entities)的基礎(chǔ)。編譯器可以交叉檢測(cè)我們提供的實(shí)參是否符合于指定的函數(shù)需要的形參。這同時(shí)也導(dǎo)致了軟件更好的自記錄(self-document)。從相似的名字選擇出一個(gè)給指定的實(shí)體通常都不會(huì)很容易,但它的好處確實(shí)值得我們這樣去做。
             
             [Wiener95]中提供了一個(gè)例子用以展示重載虛擬函數(shù)可能出現(xiàn)的問(wèn)題:

             class Parent
             {
              public:
               virutal int doIt( int v )
               {
                return v * v;
               }
             };
             
             class Child: public Parent
             {
              public:
               int doIt( int v, int av = 20 )
               {
                return v * av;
               }
             };
             
             int main()
             {
              int i;
              Parent *p = new Child();
              i = p->doIt(3);
              return 0;
             }
             
             當(dāng)程序執(zhí)行完后i會(huì)等于多少呢?有人可能會(huì)認(rèn)為是60,然而結(jié)果卻是9。這是因?yàn)樵贑hild中doIt的簽名與在Parent中的不一致,它并沒(méi)有重寫Parent中的doIt,而僅僅是重載了它,在這種情況下,缺省值沒(méi)有任何作用。

            再來(lái)看看這個(gè)例子,絕對(duì)讓你抓狂,猜猜看輸出的i和j值是多少?

            #include <stdio.h>

            class PARENT
            {
            public:
                virtual int doIt( int v, int av = 10 )
                {
                     return v * v;
                }
            };

            class CHILD : public PARENT
            {
            public:
                int doIt( int v, int av = 20 )
                {
                     return v * av;
                }
            };

            int main()
            {
                PARENT *p = new CHILD();

                int i = p->doIt(3);
                printf("i = %d\n", i);

                CHILD* q = new CHILD();

                int j = q->doIt(3);
                printf("j = %d\n", j);

                return 0;
            }
             
             Java也提供了方法重載,不同的方法可以擁有同樣的名字及不同的簽名。
             
             在Eiffel中沒(méi)有引入新的技術(shù),而是使用范型化、繼承及重定義等。Eiffel提供了協(xié)變式的簽名方式,這意味著在子類的函數(shù)中不需要完全符合父類中的簽名,但是通過(guò)Eiffel的強(qiáng)類型檢測(cè)技術(shù)可以使得它們彼此相匹配。

            繼承的本質(zhì)

            繼承關(guān)系是一種耦合度很高的關(guān)系,它與組合及一般化(genericity)一樣,提供了OO中的一種基本方法,用以將不同的軟件組件組合起來(lái)。一個(gè)類的實(shí)例同時(shí)也是那個(gè)類的所有的祖先的實(shí)例。為了保證面向?qū)ο笤O(shè)計(jì)的有效性,我們應(yīng)該保存下這種關(guān)系的一致性。在子類中的每一次重新定義都應(yīng)該與在其祖先類中的最初定義進(jìn)行一致性檢查。子類中應(yīng)該保存下其祖先類的需求。如果存在著不能被保存的需求,就說(shuō)明了系統(tǒng)的設(shè)計(jì)有錯(cuò)誤,或者是在系統(tǒng)中此處使用繼承是不恰當(dāng)?shù)摹S捎诶^承是面向?qū)ο笤O(shè)計(jì)的基礎(chǔ),所以才會(huì)要求有一致性檢測(cè)。C++中對(duì)于非虛擬函數(shù)重載的實(shí)現(xiàn), 意味著編譯器將不會(huì)為其進(jìn)行一致性檢測(cè)。C++并沒(méi)有提供面向?qū)ο笤O(shè)計(jì)的這方面的保證。

            繼承被分成"語(yǔ)法"繼承和"語(yǔ)義"繼承兩部分。 Saake等人將其描述如下:"語(yǔ)法繼承表示為結(jié)構(gòu)或方法定義的繼承,并且因此與代碼的重復(fù)使用(以及重寫被繼承方法的代碼)聯(lián)系起來(lái)。語(yǔ)義繼承表示為對(duì)對(duì)象語(yǔ)義(即對(duì)象自己)的繼承,。這種繼承形式可以從語(yǔ)義的數(shù)據(jù)模型中被得知,在此它被用于代表在一個(gè)應(yīng)用程序的若干個(gè)角色中出現(xiàn)的一個(gè)對(duì)象。"[SJE 91]。Saake等人集中研究了繼承的語(yǔ)義形式。通過(guò)是行為還是語(yǔ)義的繼承方式的判斷,表示了對(duì)象在系統(tǒng)中所扮的角色。
             
            然而, Wegner相信代碼繼承更具有實(shí)際的價(jià)值。他將語(yǔ)法與語(yǔ)義繼承之間的區(qū)別表示為代碼和行為上的區(qū)別[Weg 91](p43)。他認(rèn)為這樣的劃分不會(huì)引起一方與另一方的兼容,并且還經(jīng)常與另一方不一致。Wegner同樣也提出這樣的問(wèn)題:"應(yīng)該怎樣抑制對(duì)繼承屬性的修改?"代碼繼承為模塊化(modularisation)提供一個(gè)基礎(chǔ)。行為繼承則依賴于"is-a"關(guān)系。這兩種繼承方式在合適處都十分有用。它們都要求進(jìn)行一致性的檢測(cè),這與實(shí)際上的有意義的繼承密不可分。

            看起來(lái)在語(yǔ)義保持關(guān)系中那些限制最多的形式中,繼承似乎是其中最強(qiáng)的形式;子類應(yīng)該保存祖先類中的所有假設(shè)。

            Meyer [Meyer 96a and 96b]也對(duì)繼承技術(shù)進(jìn)行了分類。在他的分類法中,他指出了繼承的12種用法。這些分析也給我們?cè)趺词褂美^承提供了一個(gè)很好的判斷標(biāo)準(zhǔn),如:什么時(shí)候應(yīng)該使用繼承,什么時(shí)候不應(yīng)該它。

            軟件組件就象七巧板一樣。當(dāng)我們組裝七巧板時(shí),每一塊板的形狀必須要合適,但更重要地是,最終拼出的圖像必須要有意義,能夠被說(shuō)得通。而將軟件組件組合起來(lái)就更困難了。七巧板只是需要將原本是完整的一幅圖像重新組合起來(lái)。而對(duì)軟件組件的組合會(huì)得到什么樣的結(jié)果,是我們不可能預(yù)見(jiàn)到的。更糟的是,七巧板的每一塊通常是由不同的程序員產(chǎn)生的,這樣當(dāng)整個(gè)的系統(tǒng)被組合起來(lái)時(shí),對(duì)于它們的吻合程度的要求就更高了。

            C++中的繼承像是一塊七巧板,所有的板塊都能夠組合在一起,但是編譯器卻沒(méi)有辦法檢測(cè)最終的結(jié)果是否有意義。換句話說(shuō),C++僅為類和繼承提供了語(yǔ)法,而非語(yǔ)義。可重用的C++函數(shù)庫(kù)的緩慢出現(xiàn),暗示了C++可能會(huì)盡可能地不支持可重用性。相反的是,Java,Eiffel和Object Pascal都與函數(shù)庫(kù)包裝在一起出現(xiàn)。Object Pascal與MacApp應(yīng)用軟件框架聯(lián)系非常緊密。Java也從與Java API的耦合中解脫出來(lái),取而代之的是一個(gè)包容廣泛的函數(shù)庫(kù)。Eiffel也同樣是與一個(gè)極其全面的函數(shù)庫(kù)集成在一起,該函數(shù)庫(kù)甚至比Java的還要大。事實(shí)上函數(shù)庫(kù)的概念已經(jīng)成為一個(gè)優(yōu)先于Eiffel語(yǔ)言本身的工程,用以對(duì)所有在計(jì)算機(jī)科學(xué)中通用的結(jié)構(gòu)進(jìn)行重新分類,得到一個(gè)常用的分類法。 [Meyer 94].

            posted on 2008-06-15 22:05 cuigang 閱讀(302) 評(píng)論(0)  編輯 收藏 引用 所屬分類: 轉(zhuǎn)帖

            狠狠久久亚洲欧美专区 | 久久精品人人做人人爽电影蜜月| 亚洲欧美伊人久久综合一区二区 | 久久久WWW免费人成精品| 亚洲精品WWW久久久久久| 久久精品国产亚洲77777| 国产精品伦理久久久久久| 伊人 久久 精品| 久久―日本道色综合久久| 国产精品久久久久久久久久影院| 9久久9久久精品| 久久伊人五月丁香狠狠色| 国产成人无码精品久久久久免费| 亚洲精品美女久久久久99| 国内精品久久久久国产盗摄| 午夜不卡久久精品无码免费| 久久99亚洲综合精品首页| 久久综合88熟人妻| 亚洲精品久久久www| 99久久99久久精品国产| 久久香蕉国产线看观看精品yw| 久久久久亚洲av成人无码电影| 91视频国产91久久久| 亚洲AV日韩AV永久无码久久| 久久夜色精品国产www| Xx性欧美肥妇精品久久久久久 | 亚洲午夜福利精品久久| 九九久久精品无码专区| 精品久久久久久无码专区| 日本人妻丰满熟妇久久久久久| 久久综合久久综合亚洲| 久久精品国产99久久香蕉| 久久精品国产亚洲沈樵| 99热成人精品热久久669| 嫩草伊人久久精品少妇AV| yy6080久久| 精品国产乱码久久久久久人妻| 亚洲伊人久久综合中文成人网| 久久久综合香蕉尹人综合网| 国产成人精品久久亚洲高清不卡 | 狠狠色丁香婷综合久久|