青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

牽著老婆滿街逛

嚴(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

來源:http://blog.csdn.net/lostmouse/archive/2001/07/31/8503.aspx

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

在當(dāng)今喧囂的經(jīng)濟(jì)時代,關(guān)注一下我們的金融機(jī)構(gòu)是個不錯的主意。所以,看看下面這個有關(guān)銀行帳戶的協(xié)議類(Protocol class )(參見條款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)在提供了多種令人眼花繚亂的帳戶類型,但為簡化起見,我們假設(shè)只有一種銀行帳戶,稱為存款帳戶:

 

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

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

  

}
;

 

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

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

 

list<BankAccount*> allAccounts;         // 銀行中所有帳戶

 

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

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

 

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

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

}

 

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

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

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

 

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

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

}

 

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

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

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

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

 

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

  

}
;

 

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

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

 

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

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

}

 

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

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

 

class BankAccount  };      // 同上

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

  

}
;

class SavingsAccount: public InterestBearingAccount {

                             
// 同上

}
;

class CheckingAccount: public InterestBearingAccount {

                             
// as above

}
;

 

用圖形表示如下:

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

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

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

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

 

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

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

}

 

盡管這個循環(huán)還是包含一個討厭的轉(zhuǎn)換,但代碼已經(jīng)比過去健壯多了,因為即使又增加InterestBearingAccount新的子類到程序中,它還是可以繼續(xù)工作。

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

 

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

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

  (
*p)->creditInterest();

}

 

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

 

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

  

}
;

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

  (
*p)->creditInterest();

}

 

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

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

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

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

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

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

 

class BankAccount  };          // 和本條款開始時一樣

class SavingsAccount:               // 同上
  public BankAccount  };

class CheckingAccount:              // 同上
  public BankAccount  };

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

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

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

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


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


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

}

 

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

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

 

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



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

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

評論

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

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

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

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


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


青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            国产精品一区二区三区成人| 国产亚洲精品综合一区91| 欧美欧美午夜aⅴ在线观看| 美国成人毛片| 欧美激情中文不卡| 欧美日韩一本到| 国产精品久久久久久久9999| 国产精品久久久久久久久搜平片| 国产精品区一区| 国模精品一区二区三区| 亚洲第一在线视频| 99视频热这里只有精品免费| 亚洲一卡二卡三卡四卡五卡| 欧美亚洲免费电影| 久久久综合香蕉尹人综合网| 欧美激情久久久久久| 亚洲日本成人网| 在线一区欧美| 久久精品视频va| 欧美国产视频一区二区| 欧美视频亚洲视频| 国产亚洲综合性久久久影院| 亚洲激情视频| 亚洲系列中文字幕| 久久精品一级爱片| 亚洲福利视频一区二区| 一本色道久久综合亚洲精品高清| 午夜在线视频观看日韩17c| 久久伊人亚洲| 欧美天天在线| 伊人狠狠色j香婷婷综合| 99xxxx成人网| 久久精品国产99国产精品澳门| 欧美肥婆bbw| 亚洲伊人网站| 女主播福利一区| 国产精品乱码一区二三区小蝌蚪 | 亚洲综合不卡| 久久野战av| 一本色道久久加勒比88综合| 久久国产夜色精品鲁鲁99| 欧美日韩国产麻豆| 韩国av一区| 亚洲一卡久久| 欧美激情免费观看| 香蕉久久夜色精品国产| 欧美精品一区视频| 黑人巨大精品欧美一区二区 | 亚洲国产片色| 午夜在线播放视频欧美| 欧美精品一卡| 韩日成人在线| 亚洲女同在线| 亚洲黄一区二区| 久久精品亚洲一区| 国产精品日韩高清| 亚洲开发第一视频在线播放| 久久久综合激的五月天| 亚洲视频精品| 欧美日韩久久精品| 18成人免费观看视频| 欧美在线三级| 9l视频自拍蝌蚪9l视频成人| 麻豆国产va免费精品高清在线| 国产精品美女午夜av| 99成人免费视频| 欧美国产视频在线观看| 久久国产精品第一页| 国产伦一区二区三区色一情| 亚洲视频第一页| 亚洲人成小说网站色在线| 麻豆精品视频在线观看| 国产一区白浆| 久久精品国产99精品国产亚洲性色 | 欧美夫妇交换俱乐部在线观看| 亚洲欧美中文日韩在线| 国产精品swag| 在线视频欧美精品| 最新中文字幕亚洲| 麻豆精品在线观看| 在线激情影院一区| 久久综合九色| 久久精品99国产精品日本| 国产麻豆成人精品| 性久久久久久久久| 亚洲午夜精品福利| 欧美性猛片xxxx免费看久爱 | 开心色5月久久精品| 亚洲欧洲99久久| 国产伦精品一区二区三区免费| 亚洲一区二区网站| 亚洲视频图片小说| 国产精品蜜臀在线观看| 亚洲欧美日韩一区在线| 亚洲天堂成人| 国产欧美一区二区精品婷婷| 久久国产精品99久久久久久老狼 | 国产乱码精品一区二区三| 午夜精品亚洲一区二区三区嫩草| 亚洲午夜精品福利| 国产精品日韩久久久久| 欧美一区深夜视频| 羞羞视频在线观看欧美| 国产最新精品精品你懂的| 久久久久久电影| 久久久久久电影| 亚洲国产老妈| 亚洲精品日韩激情在线电影| 欧美日韩色婷婷| 亚洲专区在线视频| 欧美一级二区| 亚洲国产女人aaa毛片在线| 亚洲国产裸拍裸体视频在线观看乱了| 欧美va亚洲va国产综合| 一区二区三区日韩欧美| 亚洲一区精品在线| 国产综合网站| 亚洲高清影视| 欧美视频在线观看免费网址| 欧美一级精品大片| 久久久久99| 亚洲美女中文字幕| 亚洲一区在线观看免费观看电影高清| 国产一区二区三区日韩| 欧美福利专区| 欧美日韩一区二区三区在线| 久久成人综合视频| 久久三级视频| 在线视频精品一| 欧美亚洲综合网| 91久久夜色精品国产九色| 在线天堂一区av电影| 激情久久影院| 日韩网站在线看片你懂的| 国产日韩亚洲欧美| 亚洲国产mv| 国产精品最新自拍| 欧美大学生性色视频| 国产精品久久久久久妇女6080 | 麻豆精品国产91久久久久久| 亚洲一级免费视频| 久久美女性网| 亚洲专区在线| 麻豆精品传媒视频| 亚欧成人精品| 欧美国产日本| 久久漫画官网| 欧美性猛交xxxx乱大交蜜桃| 免费欧美日韩国产三级电影| 欧美日韩免费在线| 久久综合激情| 国产精品试看| 亚洲精品老司机| 国内视频精品| 一区二区三区日韩欧美| 亚洲激情一区二区三区| 小黄鸭精品密入口导航| 99精品国产一区二区青青牛奶| 欧美在线视频一区二区| 亚洲在线免费视频| 欧美刺激午夜性久久久久久久| 久久九九免费视频| 国产精品国产一区二区| 亚洲国产日韩一区二区| 黑人巨大精品欧美黑白配亚洲| 亚洲天堂网站在线观看视频| 亚洲精品乱码久久久久| 久久久人成影片一区二区三区 | 久久综合999| 国产精品乱码人人做人人爱| 亚洲黄色大片| 亚洲第一区在线观看| 亚洲欧美在线一区二区| 亚洲欧美国内爽妇网| 欧美母乳在线| 亚洲高清毛片| 亚洲国产精品久久久久久女王 | 在线观看成人av电影| 香蕉免费一区二区三区在线观看| 亚洲一区二区三区777| 欧美激情在线免费观看| 欧美黄色影院| 亚洲国产欧美一区二区三区久久| 久久aⅴ国产欧美74aaa| 久久精品国产96久久久香蕉| 国产精品美女www爽爽爽视频| 亚洲九九精品| 99视频精品在线| 欧美女同在线视频| 亚洲人成高清| 夜夜嗨av一区二区三区四季av| 毛片一区二区三区| 欧美成人一区二区三区在线观看| 在线观看日韩专区| 久久久久久香蕉网| 欧美gay视频| 亚洲国产精品一区二区www| 巨乳诱惑日韩免费av| 欧美激情国产精品| 亚洲乱码视频|