effecttive c++ 筆記

1. 讓自己習(xí)慣C++

條款01:視C++為一個(gè)語(yǔ)言聯(lián)邦

為了更好的理解C++,我們將C++分解為四個(gè)主要次語(yǔ)言:

  • C。說(shuō)到底C++仍是以C為基礎(chǔ)。區(qū)塊,語(yǔ)句,預(yù)處理器,內(nèi)置數(shù)據(jù)類型,數(shù)組,指針統(tǒng)統(tǒng)來(lái)自C。
  • Object-Oreinted C++。這一部分是面向?qū)ο笤O(shè)計(jì)之古典守則在C++上的最直接實(shí)施。類,封裝,繼承,多態(tài),virtual函數(shù)等等...
  • Template C++。這是C++泛型編程部分。
  • STL。STL是個(gè)template程序庫(kù)。容器(containers),迭代器(iterators),算法(algorithms)以及函數(shù)對(duì)象(function objects)...

請(qǐng)記?。?/strong>這四個(gè)次語(yǔ)言,當(dāng)你從某個(gè)次語(yǔ)言切換到另一個(gè),導(dǎo)致高效編程守則要求你改變策略。C++高效編程守則視狀況而變化,取決于你使用C++的哪一部分。

條款02:盡量以const,enum,inline替換#define

這條可理解為**讓編譯器代替預(yù)處理器做更多的事情。

預(yù)處理過(guò)程掃描源代碼,對(duì)其進(jìn)行初步的轉(zhuǎn)換,產(chǎn)生新的源代碼提供給編譯器。檢查包含預(yù)處理指令的語(yǔ)句和宏定義,并對(duì)源代碼進(jìn)行相應(yīng)的轉(zhuǎn)換。預(yù)處理過(guò)程還會(huì)刪除程序中的注釋和多余的空白字符??梢?jiàn)預(yù)處理過(guò)程先于編譯器對(duì)源代碼進(jìn)行處理。預(yù)處理指令是以#號(hào)開(kāi)頭的代碼行。

例:#define ASPECT_RATIO 1.653
記號(hào)名稱ASPECT_RATIO也許從未被編譯器看見(jiàn),也許在編譯器開(kāi)始處理源代碼之前它就被預(yù)處理器移走了。即編譯源代碼時(shí)ASPECT_RATIO已被1.653取代。ASPECT_RATIO可能并未進(jìn)入記號(hào)表(symbol table)。
替換:const double AspectRatio = 1.653;
好處應(yīng)該有:多了類型檢查,因?yàn)?define 只是單純的替換,而這種替換在目標(biāo)碼中可能出現(xiàn)多份1.653;改用常量絕不會(huì)出現(xiàn)相同情況。

請(qǐng)記住:對(duì)于單純常量,最好以const對(duì)象或enums替換#defines;對(duì)于形似函數(shù)的宏,最好改用inline函數(shù)替換#defines。

條款03:盡可能使用const

const的多才多藝:

  • 在classes外部修飾global或namespace作用域中的常量,或修飾文件、函數(shù)、或被聲明為static的對(duì)象。
  • 修飾classes內(nèi)部的static和non-static成員變量。
  • 可以指出指針自身所指事物或兩者都(或都不)是const。
  • 在一個(gè)函數(shù)聲明式內(nèi),const可以修飾函數(shù)返回值、各參數(shù)、函數(shù)自身(如果是成員函數(shù))
char greeting[] = "Hello";        
char *p = greeting;    //指針p及所指的字符串都可改變;        
const char *p = greeting;    //指針p本身可以改變,如p = &Anyother;p所指的字符串不可改變;        
char * cosnt p = greeting;    //指針p不可改變,所指對(duì)象可改變;
const char * const p = greeting;    //指針p及所致對(duì)象都不可改變;

請(qǐng)記?。?/strong>如果關(guān)鍵字const出現(xiàn)在星號(hào)左邊,表示被指物事常量。const char *p和char const *p兩種寫法意義一樣,都說(shuō)明所致對(duì)象為常量;如果關(guān)鍵字const出現(xiàn)在星號(hào)右邊,表示指針自身是常量。

將const實(shí)施于成員函數(shù),是為了1)判斷哪個(gè)函數(shù)可以改動(dòng)對(duì)象內(nèi)容而哪個(gè)函數(shù)不行,2)使操作const對(duì)象稱為可能。

常量對(duì)象不能調(diào)用非常量成員函數(shù),只能調(diào)用常量成員函數(shù);常量成員函數(shù)不能改變對(duì)象的成員變量,除非該變量聲明為mutable。

請(qǐng)記?。?/strong>

  • 將某些東西聲明為const可幫助編譯器偵測(cè)出錯(cuò)誤用法。const可被施加于任何作用域內(nèi)的對(duì)象、函數(shù)參數(shù)、函數(shù)返回類型、成員函數(shù)本體;
  • 編譯器強(qiáng)制實(shí)施bitwise constness,但你編寫程序時(shí)應(yīng)該使用“概念上的車輛”(conceptual constness);
  • 當(dāng)cosnt和non-const成員函數(shù)有著實(shí)質(zhì)等價(jià)的實(shí)現(xiàn)時(shí),令non-const版本調(diào)用const版本可避免代碼重復(fù)。

條款04:確定對(duì)象被使用前已先被初始化

永遠(yuǎn)在使用對(duì)象之前先將它初始化。對(duì)于無(wú)任何成員的內(nèi)置類型,你必須手工完成此事。至于內(nèi)置類型以外的任何其它東西,初始化責(zé)任落在構(gòu)造函數(shù)身上,確保每一個(gè)構(gòu)造函數(shù)都將對(duì)象的每一個(gè)成員初始化。
賦值和初始化:
C++規(guī)定,對(duì)象的成員變量的初始化動(dòng)作發(fā)生在進(jìn)入構(gòu)造函數(shù)本體之前。所以應(yīng)將成員變量的初始化置于構(gòu)造函數(shù)的初始化列表中。

ABEntry::ABEntry(const std::string& name, const std::string& address,
                                     const std::list<PhoneNumber>& phones)     
{             theName = name;                //這些都是賦值,而非初始化
              theAddress = address;          //這些成員變量在進(jìn)入函數(shù)體之前已調(diào)用默認(rèn)構(gòu)造函數(shù),接著又調(diào)用賦值函數(shù),
              thePhones = phones;            //即要經(jīng)過(guò)兩次的函數(shù)調(diào)用。
              numTimesConsulted = 0;    
}     
ABEntry::ABEntry(const std::string& name, const std::string& address,
                                    const std::list<PhoneNumber>& phones) :
        theName(name),                    //這些才是初始化
        theAddress(address),                //這些成員變量只用相應(yīng)的值進(jìn)行拷貝構(gòu)造函數(shù),所以通常效率更高。
        thePhones(phones), 
        numTimesConsulted(0)
 {    }     

所以,對(duì)于非內(nèi)置類型變量的初始化應(yīng)在初始化列表中完成,以提高效率。而對(duì)于內(nèi)置類型對(duì)象,其初始化和賦值的成本相同,但為了一致性最好也通過(guò)成員初始化表來(lái)初始化。如果成員變量時(shí)const或reference,它們就一定需要初值,不能被賦值。
C++有著十分固定的“成員初始化次序”?;惪偸窃谂缮愔氨怀跏蓟惖某蓡T變量總是以其說(shuō)明次序被初始化。所以:當(dāng)在成員初始化列表中列各成員時(shí),最好總是以其聲明次序?yàn)榇涡颉?/strong>
請(qǐng)記住:

  • 為內(nèi)置對(duì)象進(jìn)行手工初始化,因?yàn)镃++不保證初始化它們;
  • 構(gòu)造函數(shù)最好使用成員初始化列表,而不要在構(gòu)造函數(shù)本體內(nèi)使用賦值操作。初始化列表列出的成員變量,其排列次序應(yīng)該和它們?cè)陬愔械穆暶鞔涡蛳嗤?/li>
  • 為免除“跨編譯單元之初始化次序”問(wèn)題,請(qǐng)以local static對(duì)象替換non-local static對(duì)象。

2. 構(gòu)造、析構(gòu)、賦值運(yùn)算

條款05:了解C++默默編寫并調(diào)用哪些函數(shù)

如果你在類聲明時(shí)沒(méi)有聲明拷貝構(gòu)造函數(shù)、拷貝賦值操作符、析構(gòu)函數(shù)、構(gòu)造函數(shù),編譯器會(huì)自動(dòng)為你提供一份聲明。惟有當(dāng)這些函數(shù)被需要(被調(diào)用),它們才會(huì)被編譯器創(chuàng)建出來(lái)。

默認(rèn)構(gòu)造函數(shù)和析構(gòu)函數(shù)主要是給編譯器一個(gè)地方用來(lái)放置“藏身幕后”的代碼,像是調(diào)用基類和非靜態(tài)成員變量的構(gòu)造函數(shù)和析構(gòu)函數(shù);至于拷貝構(gòu)造函數(shù)和拷貝賦值操作符,編譯器創(chuàng)建的版本只是單純地將來(lái)源對(duì)象的每一個(gè)非靜態(tài)成員變量拷貝到目標(biāo)對(duì)象。

請(qǐng)記?。?/strong>

  • 編譯器可以暗自為類創(chuàng)建默認(rèn)構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、拷貝賦值操作符,以及析構(gòu)函數(shù)。
  • 如一個(gè)類聲明了一個(gè)構(gòu)造函數(shù)(無(wú)論有沒(méi)參數(shù)),編譯器就不再為它創(chuàng)建默認(rèn)構(gòu)造函數(shù)。
  • 編譯器生成的拷貝賦值操作符:對(duì)于成員變量中有指針,引用,常量類型,我們都應(yīng)考慮建立自己“合適”的拷貝賦值操作符。因?yàn)橹赶蛲瑝K內(nèi)存的指針是個(gè)潛在危險(xiǎn),引用不可改變,常量不可改變。

條款06:若不想使用編譯器自動(dòng)生成的函數(shù),就該明確拒絕

通常如果你不希望類支持某一特定技能,只要不說(shuō)明對(duì)應(yīng)函數(shù)就是了。但這個(gè)策略對(duì)拷貝構(gòu)造函數(shù)和拷貝賦值操作符卻不起作用。因?yàn)榫幾g器會(huì)“自作多情”的聲明它們,并在需要的時(shí)候調(diào)用它們。

由于編譯器產(chǎn)生的函數(shù)都是public類型,因此可以將拷貝構(gòu)造函數(shù)或拷貝賦值操作符聲明為private,來(lái)阻止人們?cè)谕獠空{(diào)用它;但是類中的成員函數(shù)和友元函數(shù)還是可以調(diào)用private函數(shù),解決方法可能是在一個(gè)專門為了阻止拷貝動(dòng)作而設(shè)計(jì)的基類。(Boost提供的那個(gè)類名為noncopyable)。

條款07:為多態(tài)基類聲明virtual析構(gòu)函數(shù)

當(dāng)基類的指針指向派生類的對(duì)象的時(shí)候,當(dāng)我們使用完,對(duì)其調(diào)用delete的時(shí)候,其結(jié)果將是未有定義——基類成分通常會(huì)被銷毀,而派生類的充分可能還留在堆里。這可是形成資源泄漏、敗壞之?dāng)?shù)據(jù)結(jié)構(gòu)、在調(diào)試器上消費(fèi)許多時(shí)間。

消除以上問(wèn)題的做法很簡(jiǎn)單:給基類一個(gè)virtual析構(gòu)函數(shù)。此后刪除派生類對(duì)象就會(huì)如你想要的那般。

任何類只要帶有virtual函數(shù)都幾乎確定應(yīng)該也有一個(gè)virtual析構(gòu)函數(shù)。如果一個(gè)類不含virtual函數(shù),通常表示它并不意圖被用做一個(gè)基類,當(dāng)類不企圖被當(dāng)做基類的時(shí)候,令其析構(gòu)函數(shù)為virtual往往是個(gè)餿主意。因?yàn)閷?shí)現(xiàn)virtual函數(shù),需要額外的開(kāi)銷(指向虛函數(shù)表的指針vptr)。

條款08:別讓異常逃離析構(gòu)函數(shù)

C++并不禁止析構(gòu)函數(shù)吐出異常,但它不鼓勵(lì)你這樣做。C++不喜歡析構(gòu)函數(shù)吐出異常。

如果可能導(dǎo)致異常:
如果拋出異常,就結(jié)束程序。(強(qiáng)迫結(jié)束程序是個(gè)合理選項(xiàng),畢竟它可以阻止異常從析構(gòu)函數(shù)傳播出去。)
捕獲異常,但什么也不做。

如果某個(gè)操作可能在失敗時(shí)拋出異常,而又存在某種需要必須處理該異常,那么這個(gè)異常必須來(lái)自析構(gòu)函數(shù)以外的某個(gè)函數(shù)。
請(qǐng)記?。?/strong>

  • 析構(gòu)函數(shù)絕對(duì)不要吐出異常。如果一個(gè)被析構(gòu)函數(shù)調(diào)用的函數(shù)可能拋出異常,析構(gòu)函數(shù)應(yīng)該捕捉任何異常,然后吞下它們(不傳播)或結(jié)束程序。
  • 如果客戶需要對(duì)某個(gè)操作函數(shù)運(yùn)行期間拋出的異常做出反應(yīng),那么類應(yīng)該提供一個(gè)普通函數(shù)(而非在析構(gòu)函數(shù)中)執(zhí)行該操作。

條款09:決不在構(gòu)造和析構(gòu)過(guò)程中調(diào)用virtual函數(shù)

你不該在構(gòu)造函數(shù)和析構(gòu)函數(shù)中調(diào)用virtual函數(shù),因?yàn)檫@樣的調(diào)用不會(huì)帶來(lái)你預(yù)想的結(jié)果。

基類的構(gòu)造函數(shù)的執(zhí)行要早于派生類的構(gòu)造函數(shù),當(dāng)基類的構(gòu)造函數(shù)執(zhí)行時(shí),派生類的成員變量尚未初始化。派生類的成員變量沒(méi)初始化,即為指向虛函數(shù)表的指針vptr沒(méi)被初始化又怎么去調(diào)用派生類的virtual函數(shù)呢?析構(gòu)函數(shù)也相同,派生類先于基類被析構(gòu),又如何去找派生類相應(yīng)的虛函數(shù)?

唯一好的做法是:確定你的構(gòu)造函數(shù)和析構(gòu)函數(shù)都沒(méi)有調(diào)用虛函數(shù),而它們調(diào)用的所有函數(shù)也不應(yīng)該調(diào)用虛函數(shù)。

解決的方法可能是:既然你無(wú)法使用虛函數(shù)從基類向下調(diào)用,那么我們可以使派生類將必要的構(gòu)造信息向上傳遞至基類構(gòu)造函數(shù)。即在派生類的構(gòu)造函數(shù)的成員初始化列表中顯示調(diào)用相應(yīng)基類構(gòu)造函數(shù),并傳入所需傳遞信息。

條款10:令operator= 返回一個(gè)reference to *this

對(duì)于賦值操作符,我們常常要達(dá)到這種類似效果,即連續(xù)賦值:

int x, y, z;
x = y = z = 15;

為了實(shí)現(xiàn)“連鎖賦值”,賦值操作符必須返回一個(gè)“引用”指向操作符的左側(cè)實(shí)參。

Widget & operator = (const Widget &rhs)
{
    ...
    return *this;
}

所有內(nèi)置類型和標(biāo)準(zhǔn)程序庫(kù)提供的類型如string,vector,complex或即將提供的類型共同遵守。

條款11:在operator =中處理“自我賦值”

看下面的例子:

 Widget& Widget::operator=(const Widget& rhs) 
 {     
     delete pb;         //這里對(duì)pb指向內(nèi)存對(duì)象進(jìn)行delete,試想 *this == rhs?情況會(huì)如何
     pb = new Bitmap(*rhs.pb);     //如果*this == rhs,那么這里還能new嗎?“大事不妙”。
     return *this; 
  }

也許以下代碼能解決以上問(wèn)題:

 Widget& Widget::operator=(const Widget& rhs)
 { 
     if (this == &rhs) 
     return *this;            //解決了自我賦值的問(wèn)題。

     delete pb; 
     pb = new Bitmap(*rhs.pb); 
     return *this; 
 }

“許多時(shí)候一群精心安排的語(yǔ)句就可以導(dǎo)出異常安全(以及自我賦值安全)的代碼”,以上代碼同樣存在異常安全問(wèn)題。如果new Bitmap導(dǎo)致異常,Widget最終會(huì)指向一塊被刪除的Bitmap。

Widget& Widget::operator=(const Widget& rhs)
 { 
     Bitmap *pOrig = pb;                //記住原先的pb
     pb = new Bitmap(*rhs.pb);      //令pb指向*pb的一個(gè)復(fù)本
     delete pOrig;                           //刪除原先的pb
     return *this;  //這樣既解決了自我賦值,又解決了異常安全問(wèn)題。自我賦值,將pb所指對(duì)象換了個(gè)存儲(chǔ)地址。
 } 

條款12:復(fù)制對(duì)象時(shí)勿忘其每一個(gè)成員

如果你在類中添加一個(gè)成員變量,你必須同時(shí)修改相應(yīng)的copying函數(shù)(所有的構(gòu)造函數(shù),拷貝構(gòu)造函數(shù)以及拷貝賦值操作符)。在派生類的構(gòu)造函數(shù),拷貝構(gòu)造函數(shù)和拷貝賦值操作符中應(yīng)當(dāng)顯示調(diào)用基類相對(duì)應(yīng)的函數(shù),否則編譯器可能又“自作聰明了”。

當(dāng)你編寫一個(gè)copying函數(shù),請(qǐng)確保:

  • 復(fù)制所有l(wèi)ocal成員變量;
  • 調(diào)用所有基類內(nèi)的適當(dāng)copying函數(shù)。

但是,我們不該令拷貝賦值操作符調(diào)用拷貝構(gòu)造函數(shù),也不該令拷貝構(gòu)造函數(shù)調(diào)用拷貝賦值操作符。想想,一個(gè)是拷貝(建立對(duì)象),一個(gè)是賦值(對(duì)象已經(jīng)存在)


3. 資源管理

所謂資源就是,一旦用了它,將來(lái)必須還給系統(tǒng)。C++程序中最常使用的資源就好似動(dòng)態(tài)分配內(nèi)存(如果你new了,卻忘了delete,會(huì)導(dǎo)致內(nèi)存泄露),但內(nèi)存只是你必須管理的眾多資源之一。其它常見(jiàn)的有文件描述符(file descriptors)、互斥器(mutex)、圖形界面中的字形和畫刷。數(shù)據(jù)庫(kù)連接以及網(wǎng)絡(luò)sockets。當(dāng)你不使用它們時(shí),記得還給系統(tǒng)。

條款13:以對(duì)象管理資源

把資源放進(jìn)對(duì)象內(nèi),我們便可依賴C++的“析構(gòu)函數(shù)自動(dòng)調(diào)用機(jī)制”確保資源被釋放。

許多資源被動(dòng)態(tài)分配于堆內(nèi)而后被用于單一區(qū)塊或函數(shù)內(nèi)。它們應(yīng)該在控制流離開(kāi)那個(gè)區(qū)塊或函數(shù)時(shí)被釋放。標(biāo)準(zhǔn)程序庫(kù)提供的auto_ptr正是針對(duì)這種形勢(shì)而設(shè)計(jì)的特制產(chǎn)品。auto_ptr是個(gè)“類指針對(duì)象”,也就是所謂的“智能指針”,其析構(gòu)函數(shù)自動(dòng)對(duì)其所指對(duì)象調(diào)用delete。

void f()
 { 
     std::auto_ptr<Investment> pInv(createInvestment()); 
     ... 
 }          //函數(shù)退出,auto_ptr調(diào)用析構(gòu)函數(shù)自動(dòng)調(diào)用delete,刪除pInv;無(wú)需顯示調(diào)用delete。

“以對(duì)象管理資源”的兩個(gè)關(guān)鍵想法:

  • 獲得資源后立刻放進(jìn)管理對(duì)象內(nèi)(如auto_ptr)。每一筆資源都在獲得的同時(shí)立刻被放進(jìn)管理對(duì)象中?!百Y源取得時(shí)機(jī)便是初始化時(shí)機(jī)”(Resource Acquisition Is Initialization;RAII)。
  • 管理對(duì)象運(yùn)用析構(gòu)函數(shù)確保資源被釋放。即一旦對(duì)象被銷毀,其析構(gòu)函數(shù)被自動(dòng)調(diào)用來(lái)釋放資源。

由于auto_ptr被銷毀時(shí)會(huì)自動(dòng)刪除它所指之物,所以不能讓多個(gè)auto_ptr同時(shí)指向同一對(duì)象。所以auto_ptr若通過(guò)copying函數(shù)復(fù)制它們,它們會(huì)變成NULL,而復(fù)制所得的指針將取得資源的唯一擁有權(quán)!

std::auto_ptr<Investment> pInv1(createInvestment()); //pInv1指向createInvestment()返回物;
std::auto_ptr<Investment> pInv2(pInv1);                      //現(xiàn)在pInv2指向?qū)ο?,而pInv1被設(shè)為NULL;
pInv1 = pInv2;                         //現(xiàn)在pInv1指向?qū)ο螅鴓In2被設(shè)為NULL;

受auto_ptr管理的資源必須絕對(duì)沒(méi)有一個(gè)以上的auto_ptr同時(shí)指向它。即“有你沒(méi)我,有我沒(méi)你”。auto_ptr的替代方案是“引用計(jì)數(shù)型智能指針”(reference-counting smart pointer;SCSP)、它可以持續(xù)跟蹤共有多少對(duì)象指向某筆資源,并在無(wú)人指向它時(shí)自動(dòng)刪除該資源。

TR1的tr1::shared_ptr就是一個(gè)"引用計(jì)數(shù)型智能指針"。

 void f()
 { 
     ... 
     std::tr1::shared_ptr<Investment>  pInv1(createInvestment()); //pInv1指向createInvestment()返回物;
     std::tr1::shared_ptr<Investment>  pInv2(pInv1);                     //pInv1,pInv2指向同一個(gè)對(duì)象;
     pInv1 = pInv2;                                      //同上,無(wú)變化
     ... 
 }         //函數(shù)退出,pInv1,pInv2被銷毀,它們所指的對(duì)象也竟被自動(dòng)釋放。

auto_ptr和tr1::shared_ptr都在其析構(gòu)函數(shù)內(nèi)做delete而不是delete[],也就意味著在動(dòng)態(tài)分配而得的數(shù)組身上使用auto_ptr或tr1::shared_ptr是個(gè)潛在危險(xiǎn),資源得不到釋放。也許boost::scoped_array和boost::shared_array能提供幫助。還有,vector和string幾乎總是可以取代動(dòng)態(tài)分配而得的數(shù)組。

請(qǐng)記住:

  • 為防止資源泄漏,請(qǐng)使用RAII對(duì)象,它們?cè)跇?gòu)造函數(shù)中獲得資源并在析構(gòu)函數(shù)中釋放資源。
  • 兩個(gè)常被使用的RAII類分別是auto_ptr和tr1::shared_ptr。后者通常是較佳選擇,因?yàn)槠淇截愋袨楸容^直觀。若選擇auto_ptr,復(fù)制動(dòng)作會(huì)使他(被復(fù)制物)指向NULL。

條款14:在資源管理類中小心拷貝行為

我們?cè)跅l款13中討論的資源表現(xiàn)在堆上申請(qǐng)的資源,而有些資源并不適合被auto_ptr和tr1::shared_ptr所管理??赡芪覀冃枰⒆约旱馁Y源管理類。

例:假設(shè)我們使用Mutex類型的互斥器對(duì)象,共有l(wèi)ock和unlock兩函數(shù)可用

void lock(Mutex *pm);     //鎖定pm所指的互斥量     
unlock(Mutex *pm);        //將pm解除鎖定

我們建立的資源管理類可能會(huì)是這樣:

 class Lock 
{
    public: 
    explicit Lock(Mutex *pm)
    : mutexPtr(pm) 
    {
         lock(mutexPtr); 
    } 
    ~Lock() 
    { 
         unlock(mutexPtr); 
    } 
    private: 
    Mutex *mutexPtr; 
}; 

“當(dāng)一個(gè)RAII對(duì)象被復(fù)制,會(huì)發(fā)生什么事?”大多數(shù)時(shí)候你會(huì)選擇一下兩種可能:

  • 禁止復(fù)制。如果復(fù)制動(dòng)作對(duì)RAII類并不合理,你便應(yīng)該禁止之。禁止復(fù)制類的copying函數(shù)參見(jiàn)條款6。
  • 對(duì)底層資源使用”引用計(jì)數(shù)法“。有時(shí)候我們又希望保有資源,直到它的最后一個(gè)使用者被銷毀。這種情況下復(fù)制RAII對(duì)象時(shí),應(yīng)該將資源的”被引用計(jì)數(shù)“遞增。tr1::shared_ptr便是如此。通常只要內(nèi)含一個(gè)tr1::shared_ptr成員變量,RAII類便可實(shí)現(xiàn)”引用計(jì)數(shù)“行為。

條款15:在資源管理類中提供對(duì)原始資源的訪問(wèn)

前幾個(gè)條款提到的資源管理類很棒。它們是你對(duì)抗資源泄漏的堡壘。但這個(gè)世界并不完美,許多APIs直接指涉資源,這時(shí)候我們需要直接訪問(wèn)原始資源。這時(shí)候需要一個(gè)函數(shù)可將RAII對(duì)象(如tr1::shared_ptr)轉(zhuǎn)換為其所內(nèi)含之原始資源。有兩種做法可以達(dá)成目標(biāo):顯示轉(zhuǎn)換和隱式轉(zhuǎn)換。

tr1::shared_ptr和auto_ptr都提供一個(gè)get成員函數(shù),用來(lái)執(zhí)行顯示轉(zhuǎn)換,也就是返回智能指針內(nèi)部的原始指針(的復(fù)件)。就像所有智能指針一樣, tr1::shared_ptr和auto_ptr也重載了指針取值操作符(operator->和operator),它們?cè)试S隱式轉(zhuǎn)換至底部原始指針。(即在對(duì)智能指針對(duì)象實(shí)施->和操作時(shí),實(shí)際被轉(zhuǎn)換為被封裝的資源的指針。)

class Font 
{
    public: 
    ... 
    FontHandle get() const         //FontHandle 是資源;    顯示轉(zhuǎn)換函數(shù)
    { 
        return f; 
    }
    operator FontHandle() const         //隱式轉(zhuǎn)換    這個(gè)值得注意,可能引起“非故意之類型轉(zhuǎn)換”
    { 
        return f; 
    } 
    ... 
}; 

是否該提供一個(gè)顯示轉(zhuǎn)換函數(shù)(例如get成員函數(shù))將RAII類轉(zhuǎn)換為其底部資源,或是應(yīng)該提供隱式轉(zhuǎn)換,答案主要取決于RAII類被設(shè)計(jì)執(zhí)行的特定工作,以及它被使用的情況。顯示轉(zhuǎn)換可能是比較受歡迎的路子,但是需要不停的get,get;而隱式轉(zhuǎn)換又可能引起“非故意之類型轉(zhuǎn)換”。

條款16:成對(duì)使用new和delete時(shí)要采取相同形式

當(dāng)我們使用new,有兩件事情發(fā)生:第一,內(nèi)存被分配出來(lái);第二,針對(duì)此內(nèi)存會(huì)有一個(gè)(或更多)構(gòu)造函數(shù)被調(diào)用。當(dāng)你使用delete,也有兩件事發(fā)生:針對(duì)此內(nèi)存會(huì)有一個(gè)(或多個(gè))析構(gòu)函數(shù)被調(diào)用,然后內(nèi)存才被釋放。delete的最大問(wèn)題在于:即將被刪除的內(nèi)存之內(nèi)究竟有多少對(duì)象?這個(gè)問(wèn)題的答案決定了有多少個(gè)析構(gòu)函數(shù)必須被調(diào)用起來(lái)。

解決以上問(wèn)題事實(shí)上很簡(jiǎn)單:如果你調(diào)用new時(shí)使用[],你必須在對(duì)應(yīng)調(diào)用delete時(shí)也使用[]。如果你調(diào)用new時(shí)沒(méi)有使用[],那么也不該在對(duì)應(yīng)調(diào)用delete時(shí)使用[]。

條款17:以獨(dú)立語(yǔ)句將newed對(duì)象置入智能指針

為了避免資源泄漏的危險(xiǎn),最好在單獨(dú)語(yǔ)句內(nèi)以智能指針存儲(chǔ)newed所得對(duì)象。

int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);

std::tr1::shared_ptr<Widget> pw(new Widget);    //即在傳入函數(shù)之前對(duì)智能指針初始化,而不是在傳入?yún)?shù)中
 //對(duì)其初始化,因?yàn)槟菢涌赡芤鸩僮餍蛄械膯?wèn)題。
processWidget(pw, priority()); 

請(qǐng)記住:以獨(dú)立語(yǔ)句將newed對(duì)象存儲(chǔ)于(置入)智能指針內(nèi)。如果不這樣做,一旦異常拋出,有可能導(dǎo)致難以察覺(jué)的資源泄漏。


4. 設(shè)計(jì)與聲明

所謂軟件設(shè)計(jì),是“令軟件做出你希望它做的事情”的步驟和做法,通常以頗為一般性的構(gòu)想開(kāi)始,最終變成十足的細(xì)節(jié),以允許特殊接口的開(kāi)發(fā)。

條款18:讓接口容易被正確使用,不易被誤用

  • 好的接口很容易被正確使用,不容易被誤用。你應(yīng)該在你的所有接口中努力達(dá)成這些性質(zhì)。
  • “促進(jìn)正確使用”的辦法包括接口的一致性,以及與內(nèi)置類型的行為兼容。
  • “阻止誤用”的辦法包括建立新類型、限制類型上的操作,束縛對(duì)象值,以及消除客戶的資源管理責(zé)任。
  • tr1::shared_ptr支持定制刪除器。這可防范DLL問(wèn)題,可被用來(lái)自動(dòng)解除互斥量等等。

條款19:設(shè)計(jì)class猶如設(shè)計(jì)type

設(shè)計(jì)一個(gè)良好的類,或者稱作類型,考慮一下設(shè)計(jì)規(guī)范:

  • 新類型的對(duì)象應(yīng)該如何被創(chuàng)建和銷毀?
  • 對(duì)象的初始化和對(duì)象的賦值該有什么樣的差別?
  • 新類型的對(duì)象如果被passed by value(值傳遞),意味著什么?
  • 什么是新類型的“合法值”?
  • 你的新類型需要配合某個(gè)繼承圖系嗎?
  • 你的新類型需要什么樣的轉(zhuǎn)換?
  • 什么樣的操作符和函數(shù)對(duì)此新類型而言是合理的?
  • 什么樣的標(biāo)準(zhǔn)函數(shù)應(yīng)該駁回?
  • 誰(shuí)該取用新類型的成員?
  • 什么是新類型的“未聲明接口”?
  • 你的新類型有多么一般化?
  • 你真的需要一個(gè)新類型嗎?

條款20:寧以pass-by-reference-to-const替代psss-by-value

缺省情況下C++以by value方式傳遞對(duì)象至函數(shù)。除非你另外指定,否則函數(shù)參數(shù)都是以實(shí)際實(shí)參的副本為初值,而調(diào)用端所獲得的亦是返回值的一個(gè)副本。這些副本由對(duì)象的拷貝構(gòu)造函數(shù)產(chǎn)生。所以在以對(duì)象為by value時(shí),可能會(huì)調(diào)用相應(yīng)的構(gòu)造函數(shù)(成員對(duì)象的構(gòu)造、基類對(duì)象的構(gòu)造),然后調(diào)用對(duì)應(yīng)的析構(gòu)函數(shù)。所以以by value的形式開(kāi)銷還是比較大的。

如果我們用pass-by-reference-to-const,例如:bool validateStudent(const Student& s);,這種傳遞方式效率高得多:沒(méi)有任何構(gòu)造函數(shù)或析構(gòu)函數(shù)被調(diào)用,因?yàn)闆](méi)有任何新對(duì)象被創(chuàng)建。以傳引用方式傳遞參數(shù)也可以避免對(duì)象切割問(wèn)題:即當(dāng)一個(gè)派生類對(duì)象以傳值的方式傳遞并被視為一個(gè)基類對(duì)象,基類對(duì)象的拷貝構(gòu)造函數(shù)會(huì)被調(diào)用,而“造成此對(duì)象的行為像個(gè)派生類對(duì)象”的那些特化性質(zhì)全被切割掉了,僅僅留下了基類對(duì)象。這一般不是你想要的。

所以我們一般的做法應(yīng)該是這樣:內(nèi)置對(duì)象和STL的迭代器和函數(shù)對(duì)象,我們一般以傳值的方式傳遞,而其它的任何東西都以傳引用的方式傳遞。

條款21:必須返回對(duì)象時(shí),別妄想返回其reference

  • 不要返回一個(gè)指向局部變量的指針或引用,因?yàn)榫植孔兞吭诤瘮?shù)返回后就沒(méi)有存在的意義。
  • 不要返回一個(gè)在堆上對(duì)象的引用,這會(huì)埋下了資源泄漏的危險(xiǎn)。誰(shuí)該對(duì)這對(duì)象實(shí)施delete呢?別把這種對(duì)資源的管理寄托完全寄托于用戶。
  • 不要返回一個(gè)被定義于函數(shù)內(nèi)部的靜態(tài)對(duì)象的引用。
  • “必須返回新對(duì)象”的函數(shù)的正確寫法是:就讓那個(gè)函數(shù)返回一個(gè)新對(duì)象。

條款22:將成員變量聲明為private

將成員變量隱藏在函數(shù)接口的背后,可以為“所有可能的實(shí)現(xiàn)”提供彈性。例如,這可使得成員變量被讀或?qū)憰r(shí)輕松通知其它對(duì)象、可以驗(yàn)證calss的約束條件以及函數(shù)的前提和事后狀態(tài)、可以在多線程環(huán)境中執(zhí)行同步控制......

不封裝意味不可改變!成員變量的封裝性與“成員變量的內(nèi)容改變時(shí)所壞量的代碼數(shù)量”成反比。

條款23:寧以non-member、non-friend替換member函數(shù)

寧可拿non-member non-friend函數(shù)替代member函數(shù)。這樣做可以增加封裝性、包裹彈性和機(jī)能擴(kuò)充性。

一般我們相當(dāng)然以為類中的成員函數(shù)更具封裝性,而實(shí)際上并不是那么一回事,因?yàn)槌蓡T函數(shù)不僅可以訪問(wèn)private成員變量,也可以取用private函數(shù)、enums、typedefs等等。而非成員非友元函數(shù)能實(shí)現(xiàn)更大的封裝性,因?yàn)樗荒茉L問(wèn)public函數(shù)。

將所有便利函數(shù)放在多個(gè)頭文件內(nèi)但隸屬同一個(gè)命名空間,意味客戶可以輕松擴(kuò)展這一組便利函數(shù)。需要做的就是添加更多non-member non-friend函數(shù)到此命名空間內(nèi)。

條款24:若所有參數(shù)皆需類型轉(zhuǎn)換,請(qǐng)為此采用non-member函數(shù)

通常,令類支持隱式類型轉(zhuǎn)換通常是個(gè)糟糕的主意。當(dāng)然這條規(guī)則有其例外,最常見(jiàn)的例外是在建立數(shù)值類型時(shí)。

const Rational operator*(const Rational& rhs) const; 
//如果定義一個(gè)有理數(shù)類,并實(shí)現(xiàn)*操作符為成員函數(shù),如上所示;那么考慮一下調(diào)用:
Rational oneHalf(1, 2); 
result = oneHalf * 2; // 正確,2被隱式轉(zhuǎn)換為Rational(2,1)
                             //編譯器眼中應(yīng)該是這樣:const Rational temp(2); result = oneHalf * temp; 
result = 2 * oneHalf; // 錯(cuò)誤,2,可不被認(rèn)為是Rational對(duì)象;因此無(wú)法調(diào)用operator*

可見(jiàn),這樣并不準(zhǔn)確,因?yàn)槌朔☉?yīng)該滿足交換律,不是嗎?所以,支持混合式算術(shù)運(yùn)算的可行之道應(yīng)該是:讓operator*成為一個(gè)non-member函數(shù),允許編譯器在每一個(gè)實(shí)參上執(zhí)行隱式類型轉(zhuǎn)換:

class Rational 
{
... // contains no operator* 
}; 
const Rational operator*(const Rational& lhs,  Rational& rhs)
{ 
return Rational(lhs.numerator() * rhs.numerator(), 
                        lhs.denominator() * rhs.denominator()); 
} 
Rational oneFourth(1, 4); 
Rational result; 
result = oneFourth * 2; 
result = 2 * oneFourth;  //這下兩個(gè)都工作的很好,通過(guò)隱式轉(zhuǎn)換實(shí)現(xiàn)

成員函數(shù)的方面是非成員函數(shù),而不是友元函數(shù)??梢杂妙愔械膒ublic接口實(shí)現(xiàn)的函數(shù),最好就是非成員函數(shù),而不是采用友元函數(shù)。
請(qǐng)記?。?/strong>如果你需要為某個(gè)函數(shù)的所有參數(shù)(包括被this指針?biāo)傅哪莻€(gè)隱喻參數(shù))進(jìn)行類型轉(zhuǎn)換,那么這個(gè)函數(shù)必須是個(gè)non-member。

條款25:考慮寫出一個(gè)不拋異常的swap函數(shù)

  • 當(dāng)std::swap對(duì)你的類型效率不高時(shí),提供一個(gè)swap成員函數(shù),并確定這個(gè)函數(shù)不拋出異常。
  • 如果你提供一個(gè)member swap,也該提供一個(gè)non-member swap用來(lái)調(diào)用前者。對(duì)于class(而非templates),也請(qǐng)?zhí)鼗痵td::swap。
  • 調(diào)用swap時(shí)應(yīng)針對(duì)std::swap使用using聲明式,然后調(diào)用swap并且不帶任何“命名空間資格修飾”。
  • 為“用戶定義類型”進(jìn)行std templates全特化是好的,但千萬(wàn)不要嘗試在std內(nèi)加入某些對(duì)std而言全新的東西。

5. 實(shí)現(xiàn)

大多數(shù)情況下,適當(dāng)提出你的類定義及函數(shù)聲明,是花費(fèi)最多心力的兩件事。盡管如此,還是有很多東西需要小心:太快定義變量可能造成效率上的拖延;過(guò)度使用轉(zhuǎn)型(casts)可能導(dǎo)致代碼變慢又難維護(hù),又招來(lái)微妙難解的錯(cuò)誤;返回對(duì)象“內(nèi)部數(shù)據(jù)之號(hào)碼牌(handls)”可能會(huì)破壞封裝并留給客戶虛吊號(hào)碼牌;為考慮異常帶來(lái)的沖擊則可能導(dǎo)致資源泄漏和數(shù)據(jù)敗壞;過(guò)度使用inline function可能引起代碼膨脹;過(guò)度耦合則可能導(dǎo)致讓人不滿意的冗長(zhǎng)build times。

條款26:盡可能延后變量定義式的出現(xiàn)時(shí)間

只要你定義了一個(gè)變量而其類型帶有一個(gè)構(gòu)造函數(shù)或析構(gòu)函數(shù),那么當(dāng)程序的控制流到達(dá)這個(gè)變量定義式時(shí),你便得承受構(gòu)造成本;當(dāng)這個(gè)變量離開(kāi)其作用域時(shí),你便得承受析構(gòu)成本。即使這個(gè)變量最終并未被使用,仍需耗費(fèi)這些成本,所以應(yīng)該盡量避免這種情形。因此,最好延后該變量的定義式,直到非得使用該變量的前一刻為止,甚至應(yīng)該嘗試延后這份定義直到能夠給它初值實(shí)參為止。

std::string encryptPassword(const std::string& password)
{ 
    using namespace std; 
    string encrypted;  //1
    if (password.length() < MinimumPasswordLength) 
    { 
        throw logic_error("Password is too short");     //注意:可能拋出異常
    }
    string encrypted; //2
     ... 
    return encrypted; 
 } 

如上代碼,encrypted在2處定義是個(gè)不錯(cuò)的選擇,因?yàn)槿绻麙伋霎惓?,那么encrypted的構(gòu)造和析構(gòu)可是做了無(wú)用功??!

條款27:盡量少做轉(zhuǎn)型動(dòng)作

C++規(guī)則的設(shè)計(jì)目標(biāo)之一是,保證“類型錯(cuò)誤”絕不可能發(fā)生。不幸的是,轉(zhuǎn)型(casts)破壞了類型系統(tǒng)。那可能導(dǎo)致任何種類的麻煩,有些容易辨識(shí),有些非常隱晦。
兩種舊式轉(zhuǎn)型:

 (T)expression    //C風(fēng)格的轉(zhuǎn)型動(dòng)作
 T(expression)    //函數(shù)風(fēng)格的轉(zhuǎn)型動(dòng)作

C++還提供四種新式轉(zhuǎn)型:

  • const_cast:通常被用來(lái)將對(duì)象的常量性轉(zhuǎn)除;即去掉const。
  • dynamic_cast:主要用來(lái)執(zhí)行“安全向下轉(zhuǎn)型”,也就是用來(lái)決定某對(duì)象是否歸屬繼承體系中的某個(gè)類型。
  • reinterpret_cast:意圖執(zhí)行低級(jí)轉(zhuǎn)型,實(shí)際動(dòng)作可能取決于編譯器,這也就表示它不可移植。
  • static_cast:用來(lái)強(qiáng)迫隱式轉(zhuǎn)換,例如將non-const轉(zhuǎn)型為const,int轉(zhuǎn)型為double等等。

盡量使用新式轉(zhuǎn)型:它們很容易在代碼中被辨識(shí)出來(lái),因而得以簡(jiǎn)化“找出類型系統(tǒng)在哪個(gè)地點(diǎn)被破壞”的過(guò)程;各轉(zhuǎn)型動(dòng)作的目標(biāo)愈窄化,編譯器愈可能診斷出錯(cuò)誤的運(yùn)用。

請(qǐng)記?。?/strong>

  • 如果可以,盡量避免轉(zhuǎn)型,特別是在注重效率的代碼中避免dynamic_casts。如果有個(gè)設(shè)計(jì)需要轉(zhuǎn)型動(dòng)作,試著發(fā)展無(wú)需轉(zhuǎn)型的替代設(shè)計(jì)。
  • 如果轉(zhuǎn)型是必要的,試著將它隱藏于某個(gè)函數(shù)背后。客戶隨后可以調(diào)用該函數(shù),而不需將轉(zhuǎn)型放進(jìn)他們自己的代碼內(nèi)。
  • 寧可使用C++-style(新式)轉(zhuǎn)型,不要使用舊式轉(zhuǎn)型。前者很容易辨識(shí)出來(lái),而且也比較有著分門別類的執(zhí)掌。

條款28:避免返回handls指向?qū)ο髢?nèi)部成分

struct RectData 
 { 
    Point ulhc; 
    Point lrhc; 
 }; 
class Rectangle 
{
    public: 
        ... 
        Point& upperLeft() const { return pData->ulhc; }//1  const只對(duì)函數(shù)內(nèi)進(jìn)行保護(hù),函數(shù)返回后呢??
        Point& lowerRight() const { return pData->lrhc; } //2  const只對(duì)函數(shù)內(nèi)進(jìn)行保護(hù),函數(shù)返回后呢??
    private: 
        std::tr1::shared_ptr<RectData> pData; 
        ... 
}; 

1,2兩函數(shù)都返回引用,指向private內(nèi)部數(shù)據(jù),調(diào)用者于是可通過(guò)這些引用更改內(nèi)部數(shù)據(jù)!這嚴(yán)重破壞了數(shù)據(jù)的封裝性,對(duì)私有成員進(jìn)行直接操作?太不可思議了!

const Point& upperLeft() const { return pData->ulhc; } //3
const Point& lowerRight() const { return pData->lrhc; } //4     

或者將1,2改為3,4,這就限制了客戶的“涂改權(quán)”,只有“讀取權(quán)”。但終究“返回一個(gè)handle代表對(duì)象內(nèi)部成分”總是危險(xiǎn)的。特別是將返回的指針或引用賦值給其它指針或引用,那么久造成了“懸空”。

請(qǐng)記?。?/strong>避免返回handles(包括reference、指針、迭代器)指向?qū)ο髢?nèi)部。遵守這個(gè)條款可增加封裝性,幫助const成員函數(shù)的行為像個(gè)const,并將發(fā)生“虛吊號(hào)碼牌”(dangling handles)的可能性降至最低。

條款29:為“異常安全”而努力是值得的

  • 異常安全函數(shù)(Exception-safe functions)即使發(fā)生異常也不會(huì)泄漏資源或允許任何數(shù)據(jù)結(jié)構(gòu)敗壞。這樣的函數(shù)區(qū)分為三種可能的保證:基本型、強(qiáng)烈型、不拋異常型。
  • “強(qiáng)烈保證”往往能夠以copy-and-swap實(shí)現(xiàn)出來(lái),但“強(qiáng)烈保證”并非對(duì)所有函數(shù)都可實(shí)現(xiàn)或具備現(xiàn)實(shí)意義。
  • 函數(shù)提供的“異常安全保證”通常最高只等于其所調(diào)用之各個(gè)函數(shù)的“異常安全保證”中的最弱者。

條款30:透徹了解inlining的里里外外

Inline函數(shù)看起來(lái)像函數(shù),動(dòng)作像函數(shù),比宏好得多,可以調(diào)用它們又不需蒙受函數(shù)調(diào)用所招致的額外開(kāi)銷。你實(shí)際獲得的比想象的還多,編譯器有能力對(duì)執(zhí)行語(yǔ)境相關(guān)最優(yōu)化。然而編寫程序就像現(xiàn)實(shí)生活一樣,沒(méi)有白吃的午餐。inline函數(shù)也不例外,這樣做可能增加你的目標(biāo)碼。

如果inline函數(shù)的本體很小,編譯器針對(duì)“函數(shù)本體”所產(chǎn)生的碼可能比針對(duì)“函數(shù)調(diào)用”所產(chǎn)出的碼更小。果真如此,將函數(shù)inlining確實(shí)可能導(dǎo)致較小的目標(biāo)碼和較高的指令高速緩存裝置擊中率。

Inline函數(shù)通常一定被置于頭文件內(nèi),因?yàn)榇蠖鄶?shù)建置環(huán)境在編譯過(guò)程中進(jìn)行inlining,而為了將一個(gè)“函數(shù)調(diào)用”替換為“被調(diào)用函數(shù)的本體”,編譯器必須知道那個(gè)函數(shù)長(zhǎng)什么樣子。Template通常也被置于頭文件內(nèi),因?yàn)樗坏┍皇褂?,編譯器為了將它具現(xiàn)化,需要知道哦啊它長(zhǎng)什么樣子。Template的具現(xiàn)化與inlining無(wú)關(guān)。如果你正在寫一個(gè)template而你認(rèn)為所有根據(jù)此template具現(xiàn)出來(lái)的函數(shù)都應(yīng)該inlined,請(qǐng)將此template聲明為inline;但如果你寫的template煤油理由要求它所具現(xiàn)的每一個(gè)函數(shù)都是inlined,就應(yīng)該避免將這個(gè)template聲明為inline。

一個(gè)表面上看似inline的函數(shù)是否真實(shí)inline,主要取決于編譯器。有時(shí)候雖然編譯器有意愿inlining某個(gè)函數(shù),還是可能為該函數(shù)生成一個(gè)函數(shù)本體(函數(shù)指針,構(gòu)造函數(shù),析構(gòu)函數(shù))。對(duì)程序開(kāi)發(fā)而言,將上述所有考慮牢記在新很是重要,但若從純粹實(shí)用觀點(diǎn)出發(fā),有一個(gè)事實(shí)比其它因素更重要:大部分調(diào)試器面對(duì)inline函數(shù)都束手無(wú)策。

這使我們?cè)跊Q定哪些函數(shù)該被聲明為inline而哪些函數(shù)不該時(shí),掌握一個(gè)合乎邏輯的策略。一開(kāi)始先不要將任何函數(shù)聲明為inline,或至少將inlining施行范圍局限在那些“一定成為inline”或“十分平淡無(wú)奇”的函數(shù)身上。

條款31:將文件間的編譯依存關(guān)系降至最低


6 繼承與面對(duì)對(duì)象設(shè)計(jì)

條款32:確定你的public繼承塑模出is-a關(guān)系

以C++進(jìn)行面向?qū)ο缶幊?,最重要的一個(gè)規(guī)則是:public inheritance(公有繼承)意味is-a(是一種)的關(guān)系。如果你令class D以public形式繼承class B,你便是告訴C++編譯器(以及你的代碼讀者)說(shuō),每一個(gè)類型為D的對(duì)象同時(shí)也是一個(gè)類型為B的對(duì)象,反之不成立。B對(duì)象可派上用場(chǎng)的任何地方,D對(duì)象一樣可以派上用場(chǎng)。

在C++領(lǐng)域中,任何函數(shù)如果期望獲得一個(gè)類型為基類的實(shí)參(而不管是傳指針或是引用),都也愿意接受一個(gè)派生類對(duì)象(而不管是傳指針或是引用)。(只對(duì)public繼承才成立。)

條款33:避免遮掩繼承而來(lái)的名稱

C++的名稱遮掩規(guī)則所做的唯一事情就是:遮掩名稱。至于名稱的類型是否相同并不重要。即只要名稱相同就覆蓋基類相應(yīng)的成員,類型和參數(shù)個(gè)數(shù)都無(wú)關(guān)緊要。

派生類的作用域嵌套在基類的作用域內(nèi)。C++的繼承關(guān)系的遮掩名稱也并不管成員函數(shù)是純虛函數(shù),非純虛函數(shù)或非虛函數(shù)等。只和名稱有關(guān)。 如果你真的需要用到基類的被名稱遮掩的函數(shù),可以使用using聲明式或轉(zhuǎn)交函數(shù)(forwarding function),引入基類的成員函數(shù)。若是基類中存在函數(shù)重載的情況,在派生類中應(yīng)注意同名函數(shù)覆蓋的問(wèn)題!

條款34:區(qū)分接口繼承和實(shí)現(xiàn)繼承

表面上直截了當(dāng)?shù)膒ublic繼承概念,經(jīng)過(guò)更嚴(yán)密的檢查之后,發(fā)現(xiàn)它由兩部分組成:函數(shù)接口繼承和函數(shù)實(shí)現(xiàn)繼承。

  • 成員函數(shù)的接口總是會(huì)被繼承。
  • 聲明一個(gè)純虛函數(shù)的目的是為了讓派生類只繼承函數(shù)接口。
  • 聲明一個(gè)虛函數(shù)的目的是讓派生類繼承該函數(shù)的接口和缺省實(shí)現(xiàn)。
  • 聲明一個(gè)非虛函數(shù)的目的是為了令派生類繼承函數(shù)的接口及一份強(qiáng)制性實(shí)現(xiàn)。

由于不同類型的聲明意味著本質(zhì)并不相同的事情,當(dāng)你聲明你的函數(shù)成員時(shí),必須謹(jǐn)慎選擇。如果將所以函數(shù)都聲明為非虛函數(shù),這將使得派生類沒(méi)有余??臻g進(jìn)行特化工作,而基類的非虛析構(gòu)函數(shù)尤其會(huì)帶來(lái)問(wèn)題,實(shí)際上任何被當(dāng)作基類來(lái)使用的類都會(huì)擁有若干虛函數(shù)。將所有成員函數(shù)聲明為虛函數(shù)也是一種常見(jiàn)的錯(cuò)誤,當(dāng)然有時(shí)也是正確的,如果你的不變性凌駕于特異性,別害怕說(shuō)出來(lái)。

條款35:考慮virtual函數(shù)以外的其它選擇

【C++】考慮virtual函數(shù)以外的其他選擇

條款36:絕不重新定義繼承而來(lái)的non-virtual函數(shù)

假設(shè)基類B中有一個(gè)非虛函數(shù)mf,派生類D在繼承實(shí)現(xiàn)時(shí)定義了自己的mf版本,將會(huì)導(dǎo)致基類的mf被隱藏。如果定義一個(gè)基類指針指向一個(gè)派生類對(duì)象并調(diào)用mf函數(shù)時(shí),其結(jié)果將會(huì)調(diào)用基類版本的mf函數(shù),而不是派生類版本的mf函數(shù)!這與虛函數(shù)的動(dòng)態(tài)綁定并不一樣,非虛函數(shù)是靜態(tài)綁定的。

條款37:絕不重新定義繼承而來(lái)的缺省參數(shù)值

對(duì)于non-virtual函數(shù),上一條款說(shuō)到,“絕不重新定義繼承而來(lái)的non-virtual函數(shù)”,而對(duì)于繼承一個(gè)帶有缺省參數(shù)值的virtual函數(shù),也是如此。即絕不重新定義繼承而來(lái)的缺省參數(shù)值。因?yàn)椋簐irtual函數(shù)是動(dòng)態(tài)綁定,而缺省參數(shù)值卻是靜態(tài)綁定。

一個(gè)指向派生類的基類指針,如果兩者的一個(gè)虛函數(shù)指定了不同的缺省參數(shù)值 ,那么,利用基類指針調(diào)用派生類虛函數(shù)值時(shí),其缺省參數(shù)值將是基類指定的!這是因?yàn)槿笔?shù)值卻是靜態(tài)綁定,而該指針是一個(gè)基類對(duì)象。

條款38:通過(guò)復(fù)合塑模出has-a或“根據(jù)某物實(shí)現(xiàn)出”

復(fù)合(composition)的意義和public繼承(is-a)完全不同。當(dāng)你的對(duì)象是你所塑造的世界中的某些事物,復(fù)合意味著has-a的關(guān)系,如人擁有名字、地址、電話等對(duì)象;當(dāng)你的對(duì)象純粹是實(shí)現(xiàn)細(xì)節(jié)上的人工制品(如鏈表、集合、互斥器等),復(fù)合則是表現(xiàn)的“由某某實(shí)現(xiàn)出”的關(guān)系,比如由鏈表實(shí)現(xiàn)出集合。

條款39、40:明智而審慎地使用私有繼承和多重繼承

私有繼承意味著“由某物實(shí)現(xiàn)出”的關(guān)系,但是實(shí)現(xiàn)中,盡可能使用復(fù)合而不是私有繼承來(lái)實(shí)現(xiàn)這一關(guān)系。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容