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

            牽著老婆滿(mǎn)街逛

            嚴(yán)以律己,寬以待人. 三思而后行.
            GMail/GTalk: yanglinbo#google.com;
            MSN/Email: tx7do#yahoo.com.cn;
            QQ: 3 0 3 3 9 6 9 2 0 .

            Effective C++ 2e Item39

            來(lái)源:http://blog.csdn.net/lostmouse/archive/2001/07/31/8503.aspx

            條款39: 避免 "向下轉(zhuǎn)換" 繼承層次

            在當(dāng)今喧囂的經(jīng)濟(jì)時(shí)代,關(guān)注一下我們的金融機(jī)構(gòu)是個(gè)不錯(cuò)的主意。所以,看看下面這個(gè)有關(guān)銀行帳戶(hù)的協(xié)議類(lèi)(Protocol class )(參見(jiàn)條款34):

             

            class Person  };

            class BankAccount {
            public:
              BankAccount(
            const Person *primaryOwner,
                          
            const Person *jointOwner);
              
            virtual ~BankAccount();

              
            virtual void makeDeposit(double amount) = 0;
              
            virtual void makeWithdrawal(double amount) = 0;

              
            virtual double balance() const = 0;

              

            }
            ;

             

            很多銀行現(xiàn)在提供了多種令人眼花繚亂的帳戶(hù)類(lèi)型,但為簡(jiǎn)化起見(jiàn),我們假設(shè)只有一種銀行帳戶(hù),稱(chēng)為存款帳戶(hù):

             

            class SavingsAccount: public BankAccount {
            public:
              SavingsAccount(
            const Person *primaryOwner,
                             
            const Person *jointOwner);
              
            ~SavingsAccount();

              
            void creditInterest();                // 給帳戶(hù)增加利息

              

            }
            ;

             

            這遠(yuǎn)遠(yuǎn)稱(chēng)不上是一個(gè)真正的存款帳戶(hù),但還是那句話(huà),現(xiàn)在什么年代?至少,它滿(mǎn)足我們現(xiàn)在的需要。

            銀行想為它所有的帳戶(hù)維持一個(gè)列表,這可能是通過(guò)標(biāo)準(zhǔn)庫(kù)(參見(jiàn)條款49)中的list類(lèi)模板實(shí)現(xiàn)的。假設(shè)列表被叫做allAccounts:

             

            list<BankAccount*> allAccounts;         // 銀行中所有帳戶(hù)

             

            和所有的標(biāo)準(zhǔn)容器一樣,list存儲(chǔ)的是對(duì)象的拷貝,所以,為避免每個(gè)BankAccount存儲(chǔ)多個(gè)拷貝,銀行決定讓allAccounts保存BankAccount的指針,而不是BankAccount本身。

            假設(shè)現(xiàn)在準(zhǔn)備寫(xiě)一段代碼來(lái)遍歷所有的帳戶(hù),為每個(gè)帳戶(hù)計(jì)算利息。你會(huì)這么寫(xiě):

             

            // 不能通過(guò)編譯的循環(huán)(如果你以前從沒(méi)
            // 見(jiàn)過(guò)使用 "迭代子" 的代碼,參見(jiàn)下文)
            for (list<BankAccount*>::iterator p = allAccounts.begin();
                 p 
            != allAccounts.end();
                 
            ++p) {

              (
            *p)->creditInterest();      // 錯(cuò)誤!

            }

             

            但是,編譯器很快就會(huì)讓你認(rèn)識(shí)到:allAccounts包含的指針指向的是BankAccount對(duì)象,而非SavingsAccount對(duì)象,所以每次循環(huán),p指向的是一個(gè)BankAccount。這使得對(duì)creditInterest的調(diào)用無(wú)效,因?yàn)閏reditInterest只是為SavingsAccount對(duì)象聲明的,而不是BankAccount。

            如果"list<BankAccount*>::iterator p = allAccounts.begin()" 在你看來(lái)更象電話(huà)線中的噪音,而不是C++,那很顯然,你以前無(wú)緣見(jiàn)識(shí)過(guò)C++標(biāo)準(zhǔn)庫(kù)中的容器類(lèi)模板。標(biāo)準(zhǔn)庫(kù)中的這一部分通常被稱(chēng)為標(biāo)準(zhǔn)模板庫(kù)(STL),你可以在條款49和M35初窺其概貌。但現(xiàn)在你只用知道,變量p工作起來(lái)就象一個(gè)指針,它將allAccounts中的元素從頭到尾循環(huán)一遍。也就是說(shuō),p工作起來(lái)就好象它的類(lèi)型是BankAccount**而列表中的元素都存儲(chǔ)在一個(gè)數(shù)組中。

            上面的循環(huán)不能通過(guò)編譯很令人泄氣。的確,allAccounts是被定義為保存BankAccount*,但要知道,上面的循環(huán)中它事實(shí)上保存的是SavingsAccount*,因?yàn)镾avingsAccount是僅有的可以被實(shí)例話(huà)的類(lèi)。愚蠢的編譯器!對(duì)我們來(lái)說(shuō)這么顯然的事情它竟然笨得一無(wú)所知。所以你決定告訴它:allAccounts真的包含的是SavingsAccount*:

             

            // 可以通過(guò)編譯的循環(huán),但很糟糕
            for (list<BankAccount*>::iterator p = allAccounts.begin();
                 p 
            != allAccounts.end();
                 
            ++p) {

              static_cast
            <SavingsAccount*>(*p)->creditInterest();

            }

             

            一切問(wèn)題迎刃而解!解決得很清晰,很漂亮,很簡(jiǎn)明,所做的僅僅是一個(gè)簡(jiǎn)單的轉(zhuǎn)換而已。你知道allAccounts指針保存的是什么類(lèi)型的指針,遲鈍的編譯器不知道,所以你通過(guò)一個(gè)轉(zhuǎn)換來(lái)告訴它,還有比這更合理的事嗎?

            在此,我要拿圣經(jīng)的故事做比喻。轉(zhuǎn)換之于C++程序員,就象蘋(píng)果之于夏娃。

            這種類(lèi)型的轉(zhuǎn)換 ---- 從一個(gè)基類(lèi)指針到一個(gè)派生類(lèi)指針 ---- 被稱(chēng)為 "向下轉(zhuǎn)換",因?yàn)樗蛳罗D(zhuǎn)換了繼承的層次結(jié)構(gòu)。在剛看到的例子中,向下轉(zhuǎn)換碰巧可以工作;但正如下面即將看到的,它將給今后的維護(hù)人員帶來(lái)惡夢(mèng)。

            還是回到銀行的話(huà)題上來(lái)。受到存款帳戶(hù)業(yè)務(wù)大獲成功的激勵(lì),銀行決定再推出支票帳戶(hù)業(yè)務(wù)。另外,假設(shè)支票帳戶(hù)和存款帳戶(hù)一樣,也要負(fù)擔(dān)利息:

             

            class CheckingAccount: public BankAccount {
            public:
              
            void creditInterest();    // 給帳戶(hù)增加利息

              

            }
            ;

             

            不用說(shuō),allAccounts現(xiàn)在是一個(gè)包含存款和支票兩種帳戶(hù)指針的列表。于是,上面所寫(xiě)的計(jì)算利息的循環(huán)轉(zhuǎn)瞬間有了大麻煩。

            第一個(gè)問(wèn)題是,雖然新增了一個(gè)CheckingAccount,但即使不去修改循環(huán)代碼,編譯還是可以繼續(xù)通過(guò)。因?yàn)榫幾g器只是簡(jiǎn)單地聽(tīng)信于你所告訴它們(通過(guò)static_cast)的一切:*p指向的是SavingsAccount*。誰(shuí)叫你是它的主人呢?這會(huì)給今后維護(hù)帶來(lái)第一個(gè)惡夢(mèng)。維護(hù)期第二個(gè)惡夢(mèng)在于,你一定想去解決這個(gè)問(wèn)題,所以你會(huì)寫(xiě)出這樣的代碼:

             

            for (list<BankAccount*>::iterator p = allAccounts.begin();
                 p 
            != allAccounts.end();
                 
            ++p) {

              
            if (*p 指向一個(gè) SavingsAccount)
                static_cast
            <SavingsAccount*>(*p)->creditInterest();
              
            else
                static_cast
            <CheckingAccount*>(*p)->creditInterest();

            }

             

            任何時(shí)候發(fā)現(xiàn)自己寫(xiě)出 "如果對(duì)象屬于類(lèi)型T1,做某事;但如果屬于類(lèi)型T2,做另外某事" 之類(lèi)的代碼,就要扇自己一個(gè)耳光。這不是C++的做法。是的,在C,Pascal,甚至Smalltalk中,它是很合理的做法,但在C++中不是。在C++中,要使用虛函數(shù)。

            記得嗎?對(duì)于一個(gè)虛函數(shù),編譯器可以根據(jù)所使用對(duì)象的類(lèi)型來(lái)保證正確的函數(shù)調(diào)用。所以不要在代碼中隨處亂扔條件語(yǔ)句或開(kāi)關(guān)語(yǔ)句;讓編譯器來(lái)為你效勞。如下所示:

             

            class BankAccount  };      // 同上

            // 一個(gè)新類(lèi),表示要支付利息的帳戶(hù)
            class InterestBearingAccount: public BankAccount {
            public:
              
            virtual void creditInterest() = 0;

              

            }
            ;

            class SavingsAccount: public InterestBearingAccount {

                                         
            // 同上

            }
            ;

            class CheckingAccount: public InterestBearingAccount {

                                         
            // as above

            }
            ;

             

            用圖形表示如下:

                                     BankAccount
                                              ^
                                              |
                             InterestBearingAccount
                                             /\
                                            /  \
                                           /    \
                 CheckingAccount   SavingsAccount

            因?yàn)榇婵詈椭辟~戶(hù)都要支付利息,所以很自然地想到把這一共同行為轉(zhuǎn)移到一個(gè)公共的基類(lèi)中。但是,如果假設(shè)不是所有的銀行帳戶(hù)都需要支付利息(以我的經(jīng)驗(yàn),這當(dāng)然是個(gè)合理的假設(shè)),就不能把它轉(zhuǎn)移到BankAccount類(lèi)中。所以,要為BankAccount引入一個(gè)新的子類(lèi)InterestBearingAccount,并使SavingsAccoun和CheckingAccount從它繼承。

            存款和支票賬戶(hù)都要支付利息的事實(shí)是通過(guò)InterestBearingAccount的純虛函數(shù)creditInterest來(lái)體現(xiàn)的,它要在子類(lèi)SavingsAccount和CheckingAccount中重新定義。

            有了新的類(lèi)層次結(jié)構(gòu),就可以這樣來(lái)重寫(xiě)循環(huán)代碼:

             

            // 好一些,但還不完美
            for (list<BankAccount*>::iterator p = allAccounts.begin();
                 p 
            != allAccounts.end();
                 
            ++p) {

              static_cast
            <InterestBearingAccount*>(*p)->creditInterest();

            }

             

            盡管這個(gè)循環(huán)還是包含一個(gè)討厭的轉(zhuǎn)換,但代碼已經(jīng)比過(guò)去健壯多了,因?yàn)榧词褂衷黾覫nterestBearingAccount新的子類(lèi)到程序中,它還是可以繼續(xù)工作。

            為了完全消除轉(zhuǎn)換,就必須對(duì)設(shè)計(jì)做一些改變。一種方法是限制帳戶(hù)列表的類(lèi)型。如果能得到一列InterestBearingAccount對(duì)象而不是BankAccount對(duì)象,那就太好了:

             

            // 銀行中所有要支付利息的帳戶(hù)
            list<InterestBearingAccount*> allIBAccounts;

            // 可以通過(guò)編譯且現(xiàn)在將來(lái)都可以工作的循環(huán)
            for (list<InterestBearingAccount*>::iterator p =
                    allIBAccounts.begin();
                 p 
            != allIBAccounts.end();
                 
            ++p) {

              (
            *p)->creditInterest();

            }

             

            如果不想用上面這種 "采用更特定的列表" 的方法,那就讓creditInterest操作使用于所有的銀行帳戶(hù),但對(duì)于不用支付利息的帳戶(hù)來(lái)說(shuō),它只是一個(gè)空操作。這個(gè)方法可以這樣來(lái)表示:

             

            class BankAccount {
            public:
              
            virtual void creditInterest() {}

              

            }
            ;

            class SavingsAccount: public BankAccount  };
            class CheckingAccount: public BankAccount  };
            list
            <BankAccount*> allAccounts;
            // 看啊,沒(méi)有轉(zhuǎn)換!
            for (list<BankAccount*>::iterator p = allAccounts.begin();
                 p 
            != allAccounts.end();
                 
            ++p) {

              (
            *p)->creditInterest();

            }

             

            要注意的是,虛函數(shù)BankAccount::creditInterest提供一個(gè)了空的缺省實(shí)現(xiàn)。這可以很方便地表示,它的行為在缺省情況下是一個(gè)空操作;但這也會(huì)給它本身帶來(lái)難以預(yù)見(jiàn)的問(wèn)題。想知道內(nèi)幕,以及如何消除這一危險(xiǎn),請(qǐng)參考條款36。還要注意的是,creditInterest是一個(gè)(隱式的)內(nèi)聯(lián)函數(shù),這本身沒(méi)什么問(wèn)題;但因?yàn)樗瑫r(shí)又是一個(gè)虛函數(shù),內(nèi)聯(lián)指令就有可能被忽略。條款33解釋了為什么。

            正如上面已經(jīng)看到的,"向下轉(zhuǎn)換" 可以通過(guò)幾種方法來(lái)消除。最好的方法是將這種轉(zhuǎn)換用虛函數(shù)調(diào)用來(lái)代替,同時(shí),它可能對(duì)有些類(lèi)不適用,所以要使這些類(lèi)的每個(gè)虛函數(shù)成為一個(gè)空操作。第二個(gè)方法是加強(qiáng)類(lèi)型約束,使得指針的聲明類(lèi)型和你所知道的真的指針類(lèi)型之間沒(méi)有出入。為了消除向下轉(zhuǎn)換,無(wú)論費(fèi)多大工夫都是值得的,因?yàn)橄蛳罗D(zhuǎn)換難看、容易導(dǎo)致錯(cuò)誤,而且使得代碼難于理解、升級(jí)和維護(hù)(參見(jiàn)條款M32)。

            至此,我所說(shuō)的都是事實(shí);但,不是全部事實(shí)。有些情況下,真的不得不執(zhí)行向下轉(zhuǎn)換。

            例如,假設(shè)還是面臨本條款開(kāi)始的那種情況,即,allAccounts保存BankAccount指針,creditInterest只是為SavingsAccount對(duì)象定義,要寫(xiě)一個(gè)循環(huán)來(lái)為每個(gè)帳戶(hù)計(jì)算利息。進(jìn)一步假設(shè),你不能改動(dòng)這些類(lèi);你不能改變BankAccount,SavingsAccount或allAccounts的定義。(如果它們?cè)谀硞€(gè)只讀的庫(kù)中定義,就會(huì)出現(xiàn)這種情況)如果是這樣的話(huà),你就只有使用向下轉(zhuǎn)換了,無(wú)論你認(rèn)為這個(gè)辦法有多丑陋。

            盡管如此,還是有比上面那種原始轉(zhuǎn)換更好的辦法。這種方法稱(chēng)為 "安全的向下轉(zhuǎn)換",它通過(guò)C++的dynamic_cast運(yùn)算符(參見(jiàn)條款M2)來(lái)實(shí)現(xiàn)。當(dāng)對(duì)一個(gè)指針使用dynamic_cast時(shí),先嘗試轉(zhuǎn)換,如果成功(即,指針的動(dòng)態(tài)類(lèi)型(見(jiàn)條款38)和正被轉(zhuǎn)換的類(lèi)型一致),就返回新類(lèi)型的合法指針;如果dynamic_cast失敗,返回空指針。

            下面就是加上了 "安全向下轉(zhuǎn)換" 的例子:

             

            class BankAccount  };          // 和本條款開(kāi)始時(shí)一樣

            class SavingsAccount:               // 同上
              public BankAccount  };

            class CheckingAccount:              // 同上
              public BankAccount  };

            list
            <BankAccount*> allAccounts;     // 看起來(lái)應(yīng)該熟悉些了吧

            void error(const string& msg);      // 出錯(cuò)處理函數(shù);
                                                
            // 見(jiàn)下文

            // 嗯,至少轉(zhuǎn)換很安全
            for (list<BankAccount*>::iterator p = allAccounts.begin();
                 p 
            != allAccounts.end();
                 
            ++p) {

              
            // 嘗試將*p安全轉(zhuǎn)換為SavingsAccount*;
              
            // psa的定義信息見(jiàn)下文
              if (SavingsAccount *psa =
                    dynamic_cast
            <SavingsAccount*>(*p)) {
                psa
            ->creditInterest();
              }


              
            // 嘗試將它安全轉(zhuǎn)換為CheckingAccount
              else if (CheckingAccount *pca =
                         dynamic_cast
            <CheckingAccount*>(*p)) {
                pca
            ->creditInterest();
              }


              
            // 未知的帳戶(hù)類(lèi)型
              else {
                error(
            "Unknown account type!");
              }

            }

             

            這種方法遠(yuǎn)不夠理想,但至少可以檢測(cè)到轉(zhuǎn)換失敗,而用dynamic_cast是無(wú)法做到的。但要注意,對(duì)所有轉(zhuǎn)換都失敗的情況也要檢查。這正是上面代碼中最后一個(gè)else語(yǔ)句的用意所在。采用虛函數(shù),就不必進(jìn)行這樣的檢查,因?yàn)槊總€(gè)虛函數(shù)調(diào)用必然都會(huì)被解析為某個(gè)函數(shù)。然而,一旦打算進(jìn)行轉(zhuǎn)換,這一切好處都化為烏有。例如,如果某個(gè)人在類(lèi)層次結(jié)構(gòu)中增加了一種新類(lèi)型的帳戶(hù),但又忘了更新上面的代碼,所有對(duì)它的轉(zhuǎn)換就會(huì)失敗。所以,處理這種可能發(fā)生的情況十分重要。大部分情況下,并非所有的轉(zhuǎn)換都會(huì)失?。坏牵坏┰试S轉(zhuǎn)換,再好的程序員也會(huì)碰上麻煩。

            上面if語(yǔ)句的條件部分,有些看上去象變量定義的東西,看到它你是不是慌張地擦了擦眼鏡?如果真這樣,別擔(dān)心,你沒(méi)看錯(cuò)。這種定義變量的方法是和dynamic_cast同時(shí)增加到C++語(yǔ)言中的。這一特性使得寫(xiě)出的代碼更簡(jiǎn)潔,因?yàn)閷?duì)psa或pca來(lái)說(shuō),它們只有在被dynamic_cast成功初始化的情況下,才會(huì)真正被用到;使用新的語(yǔ)法,就不必在(包含轉(zhuǎn)換的)條件語(yǔ)句外定義這些變量。(條款32解釋了為什么通常要避免多余的變量定義)如果編譯器尚不支持這種定義變量的新方法,可以按老方法來(lái)做:

             

            for (list<BankAccount*>::iterator p = allAccounts.begin();
                 p 
            != allAccounts.end();
                 
            ++p) {

              SavingsAccount 
            *psa;        // 傳統(tǒng)定義
              CheckingAccount *pca;       // 傳統(tǒng)定義

              
            if (psa = dynamic_cast<SavingsAccount*>(*p)) {
                psa
            ->creditInterest();
              }


              
            else if (pca = dynamic_cast<CheckingAccount*>(*p)) {
                pca
            ->creditInterest();
              }


              
            else {
                error(
            "Unknown account type!");
              }

            }

             

            當(dāng)然,從處理事情的重要性來(lái)說(shuō),把psa和pca這樣的變量放在哪兒定義并不十分重要。重要之處在于:用if-then-else風(fēng)格的編程來(lái)進(jìn)行向下轉(zhuǎn)換比用虛函數(shù)要遜色得多,應(yīng)該將這種方法保留到萬(wàn)不得已的情況下使用。運(yùn)氣好的話(huà),你的程序世界里將永遠(yuǎn)看不到這樣悲慘荒涼的景象。



            Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=8503

            posted on 2007-08-30 01:44 楊粼波 閱讀(217) 評(píng)論(2)  編輯 收藏 引用

            評(píng)論

            # re: Effective C++ 2e Item39 2007-08-30 18:14 junyan

            不容易涅。。。。一般的做帳都不容易了,還要編成程序。。??植腊?。。。加油~~飄過(guò)~!*_*  回復(fù)  更多評(píng)論   

            # re: Effective C++ 2e Item39 [未登錄](méi) 2007-08-30 23:55 楊粼波

            恩恩。。。  回復(fù)  更多評(píng)論   


            只有注冊(cè)用戶(hù)登錄后才能發(fā)表評(píng)論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問(wèn)   Chat2DB   管理


            久久天堂AV综合合色蜜桃网| 久久香蕉国产线看观看精品yw| 久久精品免费大片国产大片| 国产成人久久精品二区三区| 国产精品久久久久久久久久免费| 国产三级精品久久| 亚洲第一永久AV网站久久精品男人的天堂AV | 最新久久免费视频| 精品人妻伦九区久久AAA片69| 久久久久无码精品国产| 狠狠色丁香久久综合婷婷| 91精品国产色综久久| 人妻无码久久精品| 天堂久久天堂AV色综合| 香蕉久久一区二区不卡无毒影院| 久久人人爽人爽人人爽av| 亚洲人成精品久久久久| 精品久久久久久国产| 久久天天躁狠狠躁夜夜2020老熟妇 | 久久成人影院精品777| 人人狠狠综合88综合久久| 无码日韩人妻精品久久蜜桃| 久久九九有精品国产23百花影院| 久久精品国产99久久香蕉| 一本色道久久88精品综合 | 久久久久青草线蕉综合超碰| 久久国产精品无码HDAV| 久久久久国产亚洲AV麻豆| 亚洲AV无码一区东京热久久 | 中文字幕人妻色偷偷久久| 久久福利青草精品资源站| 伊人久久大香线蕉综合热线| 99久久国产热无码精品免费 | 久久久久国产一级毛片高清版| 日本精品久久久久影院日本 | 性做久久久久久久久| 久久国产色AV免费看| 亚洲伊人久久成综合人影院 | 久久人人爽人人人人片av| 日本一区精品久久久久影院| 久久久久亚洲av综合波多野结衣 |