Effective C++ 入門(mén)學(xué)習(xí)筆記
讓自己習(xí)慣C++
條款01:視C++為一個(gè)語(yǔ)言聯(lián)邦
可以將C++看作四部分組成,因?yàn)槊總€(gè)部分的思路都不同,分而治之效果更佳:1.C語(yǔ)言。C++仍以C為基礎(chǔ)2.objected-oriented C++。面向?qū)ο缶幊?,?lèi)、封裝、繼承、多態(tài)3.template C++。C++泛型編程、模板元編程的基礎(chǔ)4.STL。容器、迭代器、算法
通常,內(nèi)置類(lèi)型通過(guò)值傳遞比引用效率更高;而自定義類(lèi)型通過(guò)引用比值傳遞效率更高。
條款02:盡量以const、enum、inline替換 #define
#define 最好只用于復(fù)雜變量名替換。
條款03:盡可能使用const
通??梢詫onst理解為只讀權(quán)限。使用const有以下好處:1.讓編譯器參與幫助檢測(cè)變量的權(quán)限。當(dāng)使用了const聲明變量后,就不必?fù)?dān)心該變量被改變了,因?yàn)榫幾g器會(huì)幫你檢測(cè);2.讓使用者明白。當(dāng)參數(shù)中使用了const后,使用者會(huì)很容易知道這個(gè)參數(shù)是只讀還是讀寫(xiě),這樣就可以判斷是入?yún)⑦€是出參了,如果不聲明const,恐怕使用者都會(huì)進(jìn)去看一眼。
const可以批量設(shè)置變量屬性,如一個(gè)結(jié)構(gòu)體被聲明為const,這時(shí)結(jié)構(gòu)體內(nèi)成員都將成為只讀權(quán)限。如果此時(shí)結(jié)構(gòu)體內(nèi)某個(gè)成員需要可寫(xiě)屬性,就可以使用 mutable 關(guān)鍵字來(lái)解除限制,使其永久可寫(xiě)。
對(duì)于成員函數(shù)中使用后置const的行為,如 void printall() const ,主要是讓使用者更有信心,證明這個(gè)函數(shù)只讀不寫(xiě)。
條款04:確定對(duì)象被使用前已先被初始化
對(duì)于內(nèi)置類(lèi)型:全局變量如果初始化時(shí)沒(méi)被賦值,則會(huì)被初始化為默認(rèn)值;局部變量一定要被初始化,不然其內(nèi)部存放的是上一次存放過(guò)的值,然而該值是未知的。對(duì)于內(nèi)置類(lèi)型以外的類(lèi)型:初始化的責(zé)任落在構(gòu)造函數(shù)身上。初始化構(gòu)造函數(shù)時(shí)建議使用初始化列表而不是在其體內(nèi)使用賦值語(yǔ)句。
構(gòu)造/析構(gòu)/賦值運(yùn)算
條款05:了解C++默默編寫(xiě)并調(diào)用哪些函數(shù)
當(dāng)沒(méi)有聲明時(shí),編譯器會(huì)自動(dòng)為類(lèi)創(chuàng)建默認(rèn)構(gòu)造函數(shù)、析構(gòu)函數(shù)、復(fù)制構(gòu)造函數(shù)和賦值運(yùn)算符。
條款06:若不想使用編譯器自動(dòng)生成的函數(shù),就該明確拒絕
若不想使用編譯器自動(dòng)生成的函數(shù),可將相應(yīng)的成員函數(shù)申明為private并且不予實(shí)現(xiàn)。
條款07:為多態(tài)基類(lèi)聲明虛析構(gòu)函數(shù)
如果一個(gè)類(lèi)有任何虛函數(shù),那么它就應(yīng)該有虛析構(gòu)函數(shù)。
條款08:別讓異常逃離析構(gòu)函數(shù)
析構(gòu)函數(shù)不要拋出異常,如果析構(gòu)函數(shù)中調(diào)用的函數(shù)可能拋出異常,析構(gòu)函數(shù)應(yīng)該捕捉并記錄下來(lái)然后吞掉他(不傳播)或結(jié)束程序。同時(shí)最好提供一個(gè)普通函數(shù)用來(lái)供用戶(hù)執(zhí)行可能異常的該操作。
條款09:絕不在構(gòu)造和析構(gòu)過(guò)程中調(diào)用虛函數(shù)
在構(gòu)造函數(shù)和析構(gòu)函數(shù)中不要去調(diào)用虛函數(shù),因?yàn)樽宇?lèi)在構(gòu)造/析構(gòu)時(shí),會(huì)調(diào)用父類(lèi)的構(gòu)造/析構(gòu)函數(shù),此時(shí)其中的虛函數(shù)是調(diào)用父類(lèi)的實(shí)現(xiàn),但這是父類(lèi)的虛函數(shù)可能是純虛函數(shù),即使不是,也可能不符合你想要的目的(是父類(lèi)的結(jié)果不是子類(lèi)的結(jié)果)。如果想調(diào)用父類(lèi)的構(gòu)造函數(shù)來(lái)做一些事情,替換做法是:在子類(lèi)調(diào)用父類(lèi)構(gòu)造函數(shù)時(shí),向上傳遞一個(gè)值給父類(lèi)的構(gòu)造函數(shù)。
條款10:令 operator= 返回一個(gè)*this 引用
注意這只是個(gè)協(xié)議,但建議遵守。因?yàn)樵撔问奖粌?nèi)置類(lèi)型和標(biāo)準(zhǔn)庫(kù)類(lèi)型共同遵守。
TheClass& operator=(const TheClass& rhs) {
...
return *this;
}
條款11:在 operator= 中處理“自我賦值”
由于變量有別名的存在(多個(gè)指針或引用只想一個(gè)對(duì)象),所以可能出現(xiàn)自我賦值的情況。比如 a[i] = a[j] 或 *px=*py ,可能是同一個(gè)對(duì)象賦值。一般的解決辦法是先檢測(cè)再賦值。
條款12:復(fù)制對(duì)象時(shí)勿忘其每一個(gè)成分
復(fù)制構(gòu)造函數(shù)和賦值構(gòu)造函數(shù)要確保復(fù)制了對(duì)象內(nèi)的所有成員變量和所有基類(lèi)成分,這意味著你如果自定義以上構(gòu)造函數(shù),那么每增加成員變量,都要同步修改以上構(gòu)造函數(shù),且要調(diào)用基類(lèi)的相應(yīng)構(gòu)造函數(shù)。
資源管理
條款13:以對(duì)象管理資源
為了確保一個(gè)對(duì)象在初始化后能夠最終有效被delete,最好使用shared_ptr和auto_ptr,而前者更好,因?yàn)槭腔谝糜?jì)數(shù)機(jī)制,可以在復(fù)制時(shí)保持兩個(gè)指針都指向同一對(duì)象,且只有兩個(gè)指針都銷(xiāo)毀時(shí)才delete。
這本書(shū)可能舊了,auto_ptr已經(jīng)被廢棄了。
條款14:在資源管理類(lèi)中小心copying行為
如果對(duì)想要自行管理delete(或其他類(lèi)似行為如上鎖/解鎖)的類(lèi)處理復(fù)制問(wèn)題,有以下方案,先創(chuàng)建自己的資源管理類(lèi),然后可選擇:
- 禁止復(fù)制,使用條款6的方法
- 對(duì)復(fù)制的資源做引用計(jì)數(shù)(聲明為shared_ptr),shared_ptr支持初始化時(shí)自定義刪除函數(shù)(auto_ptr不支持,總是執(zhí)行delete)
- 做真正的深復(fù)制
- 轉(zhuǎn)移資源的擁有權(quán),類(lèi)似auto_ptr,只保持新對(duì)象擁有。
條款15:在資源管理類(lèi)中提供對(duì)原始資源的訪(fǎng)問(wèn)
封裝了資源管理類(lèi)后,API有時(shí)候往往會(huì)要求直接使用其原始資源(作為參數(shù)的類(lèi)型只能接受原始資源,不接受管理類(lèi)指針),這時(shí)候就需要提供一個(gè)獲取其原始資源的方法。有顯式轉(zhuǎn)換方法(如指針的->和(*)操作,也比如自制一個(gè)getXXX()函數(shù)),還有隱式轉(zhuǎn)換方法(比如覆寫(xiě)XXX()取值函數(shù))。顯式操作比較安全,隱式操作比較方便(但容易被誤用)。
不明覺(jué)厲。
條款16:成對(duì)使用new和delete時(shí)要采取相同形式
new 對(duì)應(yīng) delete。new a[4] 對(duì)應(yīng) delete [] a。兩者的使用必須對(duì)應(yīng)
條款17:以獨(dú)立語(yǔ)句將newed對(duì)象置入智能指針
如果有函數(shù)參數(shù)接收智能指針對(duì)象,那么該智能指針對(duì)象一定要在調(diào)用該函數(shù)前用獨(dú)立語(yǔ)句去創(chuàng)建,否則在創(chuàng)建所指對(duì)象和用該對(duì)象綁定智能指針兩個(gè)操作之間,可能插入一些操作(由于C++的獨(dú)特性),這時(shí)候如果出異常,那么會(huì)造成創(chuàng)建的對(duì)象還沒(méi)來(lái)得及用智能指針修飾,也就無(wú)法被自動(dòng)回收了。
不明覺(jué)厲。
設(shè)計(jì)與聲明
條款18:讓接口容易被正確使用,不易被誤用
好的接口要容易被正確使用,不容易被誤用,符合客戶(hù)的直覺(jué)。
- 促進(jìn)正確使用的辦法包括保持接口的一致性,既包括自定義接口之間的一致性,也包括與內(nèi)置類(lèi)型行為的相似一致性。
- 阻止誤用的辦法包括建立新類(lèi)型來(lái)限制該類(lèi)型上的操作、束縛對(duì)象的值以及消除客戶(hù)管理資源的責(zé)任,以此來(lái)作為接口的參數(shù)與返回類(lèi)型。
- shared_ptr支持定制刪除函數(shù),所以可以很方便的實(shí)現(xiàn)上述問(wèn)題,以及防范DLL問(wèn)題。
條款19:設(shè)計(jì)class猶如設(shè)計(jì)type
在設(shè)計(jì)class時(shí),要考慮一系列的問(wèn)題,包括:
- 對(duì)象的創(chuàng)建和銷(xiāo)毀(構(gòu)造、析構(gòu))
- 對(duì)象的初始化與賦值(構(gòu)造、賦值操作符)
- 復(fù)制操作(復(fù)制構(gòu)造)
- 合法值(約束條件)
- 繼承體系(注意虛函數(shù))
- 支持的類(lèi)型轉(zhuǎn)換(顯示轉(zhuǎn)換、類(lèi)型轉(zhuǎn)換操作符)
- 成員函數(shù)和成員變量的可見(jiàn)范圍(public/protected/private)
- 是否用模板就能實(shí)現(xiàn)?
條款20:寧以傳遞const引用替換傳遞值
盡量用 常量引用類(lèi)型 來(lái)作為函數(shù)的參數(shù)類(lèi)型,這通常比較高效,也可以解決基類(lèi)參數(shù)類(lèi)型被賦值子類(lèi)時(shí)引起的內(nèi)容切割問(wèn)題。但對(duì)于內(nèi)置類(lèi)型和STL的迭代器與函數(shù)對(duì)象,通常編譯器會(huì)對(duì)其專(zhuān)門(mén)優(yōu)化,直接傳值類(lèi)型往往比較恰當(dāng)。
條款21:必須返回對(duì)象時(shí),別妄想返回其引用
雖然函數(shù)參數(shù)最好用引用值,但函數(shù)返回值卻不要隨便去用引用,這回造成很多問(wèn)題,比如引用的對(duì)象在函數(shù)結(jié)束后即被銷(xiāo)毀,或是需要付出很多成本和代碼來(lái)保證其不被銷(xiāo)毀且不重復(fù),這大概率沒(méi)有必要,就返回一個(gè)值/對(duì)象就好了。
條款22:將成員變量聲明為private
切記將成員變量聲明為private,這可以保證客戶(hù)訪(fǎng)問(wèn)數(shù)據(jù)的一致性、可以細(xì)微劃分訪(fǎng)問(wèn)控制、允許約束條件獲得保證,并提供類(lèi)作者充分的實(shí)現(xiàn)彈性來(lái)修改對(duì)其的處理,因?yàn)檫@保證了“封裝性”,作者可以改變實(shí)現(xiàn)和對(duì)成員變量的操作,而不改變客戶(hù)的調(diào)用方式。protected并不比public更加具有封裝性,因?yàn)閜rotected修飾的成員變量一旦修改,也會(huì)造成子類(lèi)的大量修改。
條款23:寧以非成員、非友元替換成員函數(shù)
寧可拿非成員非友元函數(shù)來(lái)替換成員函數(shù)。因?yàn)檫@種函數(shù)位于函數(shù)之外,不能訪(fǎng)問(wèn)類(lèi)的private成員變量和函數(shù),保證了封裝性(沒(méi)有增加可以看到內(nèi)部數(shù)據(jù)的函數(shù)量),此外,這些函數(shù)只要位于同一個(gè)命名空間內(nèi),就可以被拆分為多個(gè)不同的頭文件,客戶(hù)可以按需引入頭文件來(lái)獲得這些函數(shù),而類(lèi)是無(wú)法拆分的(子類(lèi)繼承與此需求不同),因此這種做法有更好的擴(kuò)充性。
條款24:若所有參數(shù)皆需類(lèi)型轉(zhuǎn)換,請(qǐng)為此采用非成員函數(shù)
如果你要為某個(gè)函數(shù)的所有參數(shù)(包括this所指對(duì)象本身)進(jìn)行類(lèi)型轉(zhuǎn)換,那么該函數(shù)必須是個(gè)非成員函數(shù)。舉個(gè)例子,你想為一個(gè)有理數(shù)類(lèi)實(shí)現(xiàn)乘法函數(shù),支持與int類(lèi)型的乘積,可以,因?yàn)閭鲄nt進(jìn)去后會(huì)調(diào)用構(gòu)造函數(shù)隱式轉(zhuǎn)換為有理數(shù)類(lèi)型,同時(shí)你想滿(mǎn)足交換律,這時(shí)就會(huì)報(bào)錯(cuò),因?yàn)閕nt類(lèi)型并沒(méi)有一個(gè)函數(shù)用來(lái)支持你的有理數(shù)類(lèi)做參數(shù)的乘法運(yùn)算。解決方案是將該乘法運(yùn)算函數(shù)作為一個(gè)非成員函數(shù),傳兩個(gè)參數(shù)進(jìn)去,這樣不管你的int放在前面還是后面,都能作為參數(shù)被轉(zhuǎn)換類(lèi)型了。但是,非成員函數(shù)不代表就一定成為友元函數(shù),能夠通過(guò)public函數(shù)調(diào)用完成功能的,就不該設(shè)為友元函數(shù),避免權(quán)力過(guò)大造成麻煩。
條款25:考慮寫(xiě)出一個(gè)不拋異常的swap函數(shù)
由于swap函數(shù)如此重要,需要特別對(duì)他做出一些優(yōu)化。常規(guī)的swap是簡(jiǎn)單全復(fù)制三次對(duì)象進(jìn)行交換(包括temp對(duì)象),如果效率足夠就用常規(guī)版。如果效率不夠,那么給你的類(lèi)提供一個(gè)成員函數(shù)swap,用來(lái)對(duì)那些復(fù)制效率低的成員變量(通常是指針)做交換。然后,提供一個(gè)非成員函數(shù)的swap來(lái)調(diào)用這個(gè)成員函數(shù),供別人調(diào)用置換。對(duì)于類(lèi)(非模板),為標(biāo)準(zhǔn)std::swap提供一個(gè)特定版本(swap是模板函數(shù),可以特化)。在使用swap時(shí),記得 using std::swap,讓編譯器可以獲取到標(biāo)準(zhǔn)swap或特化版本。編譯器會(huì)自行從所有可能性中選擇最優(yōu)版本。
實(shí)現(xiàn)
條款26:盡可能延后變量定義式的出現(xiàn)時(shí)間
直到使用它時(shí)才進(jìn)行定義。但對(duì)于循環(huán),定義在循環(huán)體前還是定義在循環(huán)體內(nèi),哪一個(gè)比較好呢?方法A:定義在循環(huán)體外
Widget w;
for(int i=0;i<n;i++)
{
w=...
}
方法A:定義在循環(huán)體內(nèi)
for(int i=0;i<n;i++)
{
Widget w(...);
}
做法A:1個(gè)構(gòu)造函數(shù)+1個(gè)析構(gòu)函數(shù)+n個(gè)賦值操作;做法B:n個(gè)構(gòu)造函數(shù)+n個(gè)析構(gòu)函數(shù)。
除非(1)你知道賦值成本比“構(gòu)造+析構(gòu)”成本低,(2)你正在處理代碼中效率高度敏感的部分,否則你應(yīng)該使用做法B。
條款27:盡量少做轉(zhuǎn)型操作
盡量避免使用轉(zhuǎn)型cast(包括C的類(lèi)型轉(zhuǎn)換和C++的四個(gè)新式轉(zhuǎn)換函數(shù)),特別是注重效率的代碼中避免用dynamic_casts。如果一定要用,試著考慮無(wú)需轉(zhuǎn)型的替代設(shè)計(jì),例如為基類(lèi)添加一個(gè)什么也不做的衍生類(lèi)使用的函數(shù),避免在使用時(shí)需要將基類(lèi)指針轉(zhuǎn)型為子類(lèi)指針。如果一定要轉(zhuǎn)型,試著將其隱藏于某個(gè)函數(shù)后,客戶(hù)調(diào)用該函數(shù)而無(wú)需自己用轉(zhuǎn)型。寧可使用C++新式轉(zhuǎn)型,也不用用C的舊式,因?yàn)樾率降母菀妆蛔⒁獾?,而且各自用途?zhuān)一。
條款28:避免返回handles指向?qū)ο髢?nèi)部成分
避免讓外部可見(jiàn)的成員函數(shù)返回handles(包括引用、指針、迭代器)指向?qū)ο髢?nèi)部(更隱私的成員變量或函數(shù)),即使返回const修飾也有風(fēng)險(xiǎn)。這一方面降低了封裝性,另一方面可能導(dǎo)致其指向的對(duì)象內(nèi)部元素被修改或銷(xiāo)毀。
條款29:為異常安全而努力是值得的
異常安全函數(shù)是指即使發(fā)生異常也不會(huì)泄露資源或者導(dǎo)致數(shù)據(jù)結(jié)構(gòu)破壞,分三種保證程度:基本保證、強(qiáng)烈保證和不拋異常型。只有基本類(lèi)型才確保了不拋異常型。對(duì)于我們自己設(shè)計(jì)的函數(shù),往往想要提供強(qiáng)烈保證,即一旦發(fā)生異常,程序的整個(gè)狀態(tài)會(huì)回到執(zhí)行函數(shù)前的狀態(tài),實(shí)現(xiàn)方法一般用復(fù)制一個(gè)副本然后執(zhí)行操作,全部成功后再替換原對(duì)象的方式來(lái)實(shí)現(xiàn)。但這一操作有時(shí)對(duì)時(shí)間和空間的消耗較大,適用性不強(qiáng)。這種情況下可以提供基本保證。函數(shù)提供的保證程度通常最高只等于其所調(diào)用的各個(gè)函數(shù)中的保證的最弱者——木桶理論。
條款30:透徹了解inline的里里外外
inline還真的和宏很像,用一個(gè)名稱(chēng)替換一段代碼。類(lèi)聲明中的帶有實(shí)現(xiàn)的成員函數(shù)就是內(nèi)聯(lián)函數(shù)。一般用于返回私有值。
只將inline用在小型、被頻繁調(diào)用的函數(shù)身上。inline會(huì)帶來(lái)體積增大的問(wèn)題,此外,不要對(duì)構(gòu)造函數(shù)、析構(gòu)函數(shù)等使用inline,即使你自己在其中寫(xiě)的代碼可能很少,編譯器卻會(huì)為他添加很多代碼。不要只因?yàn)槟0搴瘮?shù)出現(xiàn)在頭文件,就將它們聲明為inline,模板函數(shù)和inline并不是必須結(jié)對(duì)出現(xiàn)的。
條款31:將文件間的編譯依存關(guān)系降至最低
為了增加編譯速度,應(yīng)該減少類(lèi)文件之間的相互依存性(include),但是類(lèi)內(nèi)又常常使用到其他類(lèi),不得不相互依存,解決方案是:將類(lèi)的聲明和定義分開(kāi)(不同的頭文件),聲明相互依存,而定義不相依存,這樣當(dāng)定義需要變更時(shí),編譯時(shí)不需要再因?yàn)橐蕾?lài)而全部編譯。基于此構(gòu)想的兩個(gè)手段是Handle classes和Interface classes。Handle classes是一個(gè)聲明類(lèi),一個(gè)imp實(shí)現(xiàn)類(lèi),聲明類(lèi)中不涉及具體的定義,只有接口聲明,在定義類(lèi)中include聲明類(lèi),而不是繼承。而Interface classes是在接口類(lèi)中提供純虛函數(shù),作為一個(gè)抽象基類(lèi),定義類(lèi)作為其子類(lèi)來(lái)實(shí)現(xiàn)具體的定義。
繼承與面向?qū)ο笤O(shè)計(jì)
條款32:確定你的public繼承是is-a關(guān)系
public繼承意味著 is-a 關(guān)系,也就是要求,適用于基類(lèi)身上的每一件事情,是每一件,也一定適用于衍生類(lèi)身上。有時(shí)候,直覺(jué)上滿(mǎn)足這一條件的繼承關(guān)系,可能并不一定,比如,企鵝是鳥(niǎo),但并不會(huì)飛。
條款33:避免遮掩繼承而來(lái)的名稱(chēng)
就如函數(shù)作用域內(nèi)的變量會(huì)掩蓋函數(shù)作用域外的同名變量一樣。衍生類(lèi)中如果聲明了與基類(lèi)中同名的函數(shù)(無(wú)論是虛、非虛,還是其他形式),都會(huì)掩蓋掉基類(lèi)中的所有同名函數(shù),注意,是所有,包括參數(shù)不同的重載函數(shù),都會(huì)不再可見(jiàn)。此時(shí)再通過(guò)子類(lèi)使用其基類(lèi)中的重載函數(shù)(子類(lèi)沒(méi)有聲明接收該參數(shù)的重載函數(shù)時(shí)),都會(huì)報(bào)錯(cuò)。解決方案一是使用using聲明式來(lái)在子類(lèi)中聲明父類(lèi)的同名函數(shù)(重載函數(shù)不需要聲明多個(gè)),此時(shí)父類(lèi)的各重載函數(shù)就是子類(lèi)可見(jiàn)的了。二是使用轉(zhuǎn)交函數(shù),即在子類(lèi)函數(shù)的聲明時(shí)進(jìn)行定義,調(diào)用父類(lèi)的某個(gè)具體的重載函數(shù)(此時(shí)由于在聲明時(shí)定義,成為inline函數(shù)),此舉可以只讓需要的部分父類(lèi)重載函數(shù)于子類(lèi)可見(jiàn)。
條款34:區(qū)分接口繼承和實(shí)現(xiàn)繼承
Public繼承由兩部分組成:接口(interface)繼承和實(shí)現(xiàn)(implementation)繼承.
class Shape{
public:
virtual void draw() const = 0;//純虛函數(shù)
virtual void error(const std::string& msg);//非純虛函數(shù)
int ObjectID() const;//非虛函數(shù)
....
};
class Rectangle:public Shape{...};
class Ellipse:public Shape{...};
上述代碼中,類(lèi)Shape是一個(gè)抽象類(lèi),因?yàn)閐raw()是純虛函數(shù),所以客戶(hù)不能創(chuàng)建Shape的實(shí)體。純虛函數(shù)的作用是:讓派生類(lèi)只繼承函數(shù)接口,而派生類(lèi)必須提供實(shí)現(xiàn);非純虛函數(shù)的作用是:讓派生類(lèi)繼承函數(shù)接口,并提供給派生類(lèi)默認(rèn)的實(shí)現(xiàn)方法(實(shí)現(xiàn)繼承),當(dāng)派生類(lèi)不打算復(fù)寫(xiě)方法時(shí),將應(yīng)用基類(lèi)提供的默認(rèn)方法;非虛函數(shù)的作用是:派生類(lèi)不能復(fù)寫(xiě)該函數(shù)。
條款35:考慮虛函數(shù)以外的其他選擇
虛函數(shù)(本質(zhì)是希望子類(lèi)的實(shí)現(xiàn)不同)的替代方案:
- 用public的非虛函數(shù)來(lái)調(diào)用private的虛函數(shù)具體實(shí)現(xiàn),非虛函數(shù)必須為子類(lèi)繼承且不得更改,所以它決定了何時(shí)調(diào)用以及調(diào)用前后的處理;虛函數(shù)實(shí)現(xiàn)可以在子類(lèi)中覆寫(xiě),從而實(shí)現(xiàn)多態(tài)。
- 將虛函數(shù)替換為函數(shù)指針成員變量,這樣可以對(duì)同一種子類(lèi)對(duì)象賦予不同的函數(shù)實(shí)現(xiàn),或者在運(yùn)行時(shí)更改某對(duì)象對(duì)應(yīng)的函數(shù)實(shí)現(xiàn)(添加一個(gè)set函數(shù))。
- 用tr1::function成員變量替換虛函數(shù),從而允許包括函數(shù)指針在內(nèi)的任何可調(diào)用物搭配一個(gè)兼容于需求的簽名式。
- 將虛函數(shù)也做成另一個(gè)繼承體系類(lèi),然后在調(diào)用其的類(lèi)中添加一個(gè)指針來(lái)指向其對(duì)象。
本條款的啟示為:為避免陷入面向?qū)ο笤O(shè)計(jì)路上因常規(guī)而形成的凹洞中,偶爾我們需要對(duì)著車(chē)輪猛推一把。這個(gè)世界還有其他許多道路,值得我們花時(shí)間加以研究。
條款36:絕不重新定義繼承而來(lái)的非虛函數(shù)
不要重新定義繼承而來(lái)的非虛函數(shù),理論上,非虛函數(shù)的意義就在于父類(lèi)和子類(lèi)在該函數(shù)上保持一致的實(shí)現(xiàn)。
條款37:絕不重新定義繼承而來(lái)的缺省參數(shù)值
不要重新定義一個(gè)繼承而來(lái)的函數(shù)(虛函數(shù))的缺省參數(shù)的值(參數(shù)默認(rèn)值),因?yàn)楹瘮?shù)是動(dòng)態(tài)綁定(調(diào)用指針指向的對(duì)象的函數(shù)實(shí)現(xiàn)),但參數(shù)默認(rèn)值卻是靜態(tài)綁定(指針聲明時(shí)的類(lèi)型所設(shè)定的默認(rèn)參數(shù),比如基類(lèi)設(shè)定的)。這會(huì)導(dǎo)致兩者不對(duì)應(yīng),比如: Base *p = new SubClass();
條款38:通過(guò)復(fù)合表示 has-a 或者“根據(jù)某物實(shí)現(xiàn)出”的關(guān)系
注意 has-a 和 is-a 的區(qū)分。如果是 is-a 的關(guān)系,可以用繼承,但如果是 has-a 的關(guān)系,應(yīng)該將一個(gè)類(lèi)作為另一個(gè)類(lèi)的成員變量來(lái)使用,以利用該類(lèi)的能力,而不是去以繼承它的方式使用。
條款39:明智而審慎地使用private繼承
Private繼承意味著“根據(jù)某物實(shí)現(xiàn)出”,而不是 is-a 的關(guān)系。與上面的復(fù)合(has-a)很像,但比復(fù)合的級(jí)別低。當(dāng)衍生類(lèi)需要訪(fǎng)問(wèn) protected 基類(lèi)的成員,或需要重新定義繼承而來(lái)的虛函數(shù)時(shí),可以這么設(shè)計(jì)。此外,private繼承可以讓空基類(lèi)的空間最優(yōu)化。
條款40:明智而審慎地使用多重繼承
多重繼承確實(shí)有正當(dāng)使用場(chǎng)景,比如public繼承某個(gè)接口類(lèi)的接口(其接口依然是public的),private繼承某個(gè)類(lèi)的實(shí)現(xiàn)來(lái)協(xié)助實(shí)現(xiàn)(繼承來(lái)的實(shí)現(xiàn)為private,只供自己用)。虛繼承會(huì)增加大小、速度、初始化(及賦值)復(fù)雜度等成本,如果虛基類(lèi)不帶任何數(shù)據(jù),將是最具使用價(jià)值的情況。
模板與泛型編程
條款41:了解隱式接口和編譯期多態(tài)
類(lèi)和模板都支持接口和多態(tài)。類(lèi)的接口是顯式定義的——函數(shù)簽名。多態(tài)是通過(guò)虛函數(shù)在運(yùn)行期體現(xiàn)的。模板的接口是隱式的(由模板函數(shù)的實(shí)現(xiàn)代碼所決定其模板對(duì)象需要支持哪些接口),多態(tài)通過(guò)模板具現(xiàn)化和函數(shù)重載解析在編譯期體現(xiàn),也就是編譯期就可以賦予不同的對(duì)象于模板函數(shù)。
條款42:了解typename的雙重意義
聲明模板的參數(shù)時(shí),前綴關(guān)鍵字 class 和 typename 可互換,功能相同。對(duì)于嵌套從屬類(lèi)型名稱(chēng)(即依賴(lài)于模板參數(shù)類(lèi)型的一個(gè)子類(lèi)型,例如迭代器),必須用typename來(lái)修飾,但不能在模板類(lèi)的基類(lèi)列和初始化列表中修飾基類(lèi)。
條款43:學(xué)習(xí)處理模板化基類(lèi)內(nèi)的名稱(chēng)
如果基類(lèi)是模板類(lèi),那么衍生類(lèi)直接調(diào)用基類(lèi)的成員函數(shù)無(wú)法通過(guò)編譯器,因?yàn)榭赡軙?huì)有特化版的模板類(lèi)針對(duì)某個(gè)類(lèi)不聲明該接口函數(shù)。解決方法有:
條款44:將與參數(shù)無(wú)關(guān)的代碼抽離templates
任何模板代碼都不該與某個(gè)造成膨脹的參數(shù)產(chǎn)生相依關(guān)系:
條款45:運(yùn)用成員函數(shù)模板接受所有兼容類(lèi)型
真實(shí)指針允許父類(lèi)指針指向子類(lèi)對(duì)象,如果想要讓自制的智能指針也支持這種對(duì)象轉(zhuǎn)換,那就需要特殊操作,因?yàn)橐话愕哪0孱?lèi)(智能指針能指向多種對(duì)象,必然是模板類(lèi))只能以自身模板聲明的類(lèi)型來(lái)構(gòu)造。做法是聲明一個(gè)泛化構(gòu)造函數(shù),也就是定義一個(gè)模板構(gòu)造函數(shù),接收模板參數(shù),聲明一個(gè)指向的真實(shí)對(duì)象指針,聲明一個(gè)獲取該對(duì)象指針的get函數(shù),用該get函數(shù)放在初始化列表中來(lái)構(gòu)造模板類(lèi)。這樣就能使用一種類(lèi)型特化出的自制智能指針來(lái)構(gòu)造另一種類(lèi)型特化出的自制智能指針了。同時(shí),在初始化列表中編譯器會(huì)為你檢查是否允許該類(lèi)型轉(zhuǎn)換(比如只允許子類(lèi)往父類(lèi)的轉(zhuǎn)換,不能相反)。雖然這種模板構(gòu)造函數(shù)也能作為復(fù)制構(gòu)造函數(shù)使用(用相同類(lèi)型來(lái)構(gòu)造即可),但編譯器還是會(huì)當(dāng)做你沒(méi)有聲明復(fù)制構(gòu)造函數(shù),從而為你創(chuàng)建一個(gè),因此如果想要徹底控制行為,你還是需要自行聲明你的復(fù)制構(gòu)造函數(shù)和賦值構(gòu)造函數(shù)。
條款46:需要類(lèi)型轉(zhuǎn)換時(shí)請(qǐng)為模板定義非成員函數(shù)
模板類(lèi)中的模板函數(shù)不支持隱式類(lèi)型轉(zhuǎn)換,如果你在調(diào)用時(shí)傳了一個(gè)其他類(lèi)型的變量,編譯器無(wú)法幫你做類(lèi)型轉(zhuǎn)換,從而報(bào)錯(cuò)。解決方案是將該模板函數(shù)定義為模板類(lèi)內(nèi)的友元模板函數(shù),從而支持了參數(shù)的隱式轉(zhuǎn)換。如果函數(shù)的功能比較簡(jiǎn)單,可以直接inline,如果比較復(fù)雜,可以調(diào)用一個(gè)類(lèi)外的定義好的模板函數(shù)(此時(shí),友元函數(shù)已經(jīng)給參數(shù)做了類(lèi)型轉(zhuǎn)換,因此可以調(diào)用模板函數(shù)了)。
條款47:請(qǐng)使用traits classes表現(xiàn)類(lèi)型信息
對(duì)于模板函數(shù),可能對(duì)于接收參數(shù)的不同類(lèi)型,有不同的實(shí)現(xiàn)。此時(shí),可以提供一個(gè)traits class,其中包含了某一系列類(lèi)型的類(lèi)型信息(通常以枚舉區(qū)分具體類(lèi)型),然后,在該類(lèi)中實(shí)現(xiàn)接收多種traits參數(shù)的重載工具函數(shù),用來(lái)根據(jù)標(biāo)識(shí)的不同類(lèi)進(jìn)行不同的具體函數(shù)操作。這使得該行為能在編譯期就被區(qū)分。
條款48:認(rèn)識(shí)模板元編程(TMP)
TMP可將工作由運(yùn)行期移往編譯器,因而得以實(shí)現(xiàn)早期錯(cuò)誤偵測(cè)和更高的執(zhí)行效率。實(shí)現(xiàn)方式以模板為基礎(chǔ),因?yàn)槟0鍟?huì)在編譯時(shí)確定,上一條款的traits classes就是一種TMP,依靠模板函數(shù)參數(shù)不同的重載來(lái)在編譯器模擬if else(其在運(yùn)行期才會(huì)判斷)。另一個(gè)例子是用模板來(lái)在編譯器實(shí)現(xiàn)階乘:
template<unsigned n>
struct Factorial {
enum { value = n * Factorial<n-1>::value };
};
template<>
struct Factorial<0> {
enum { value = 1 };
}
用模板來(lái)實(shí)現(xiàn)遞歸從而在編譯器實(shí)現(xiàn)階乘運(yùn)算,用參數(shù)為0的特異化來(lái)做遞歸的終結(jié)。
定制 new 和 delete
條款49:了解 new-handler 的行為
在對(duì)象new操作分配內(nèi)存時(shí),如果分配失敗,默認(rèn)會(huì)返回null(老編譯器)或拋出bad_alloc 異常(新編譯器)。如果想要自定義分配失敗的操作,可以調(diào)用 set_new_handler 函數(shù)來(lái)設(shè)置new_handler。如果想要讓類(lèi)在構(gòu)造時(shí)自動(dòng)調(diào)用自定義的new_handler,并在構(gòu)造結(jié)束后回到系統(tǒng)默認(rèn)的new_handler 。可以繼承一個(gè)聲明了set_new_handler函數(shù)接口和包含設(shè)置與回歸new_handler的new函數(shù)的模板類(lèi),然后讓你的自定義類(lèi)繼承自你的類(lèi)名所特化的該模板類(lèi),從而能夠?yàn)槊恳粋€(gè)你的類(lèi)做一個(gè)特化的new_handler函數(shù)。
條款50:了解new和delete的合理替換時(shí)機(jī)
有很多理由讓你想要寫(xiě)個(gè)自定的new和delete,比如改善定制版的效能、對(duì)heap運(yùn)用錯(cuò)誤進(jìn)行調(diào)試、收集heap使用信息等。也有許多商業(yè)或開(kāi)源的內(nèi)存分配器供你使用。
條款51:編寫(xiě)new和delete時(shí)需固守常規(guī)
自定義的new應(yīng)該內(nèi)含一個(gè)無(wú)窮循環(huán),在其中嘗試分配內(nèi)存,如果失敗,就該調(diào)用new-handler以退出循環(huán)。同時(shí)它應(yīng)該有能力處理0 bytes的申請(qǐng)(可以簡(jiǎn)單判斷并改為1bytes)。Class專(zhuān)屬版本還要處理衍生類(lèi)的申請(qǐng),不要直接調(diào)用基類(lèi)的(大小不同),可以判斷并轉(zhuǎn)調(diào)普通的new函數(shù)。自定義的delete應(yīng)該可在收到null指針時(shí)不做任何事,Class專(zhuān)屬版本還應(yīng)該處理衍生類(lèi)的申請(qǐng),不要直接調(diào)用基類(lèi)的(大小不同),可以判斷并轉(zhuǎn)調(diào)普通的delete函數(shù)。
條款52:寫(xiě)了 placement new 也要寫(xiě) placement delete
如果你的new接收的參數(shù)除了必定有的size_t外還有其他,就是個(gè)placement new。delete類(lèi)似。當(dāng)創(chuàng)建對(duì)象時(shí),會(huì)先進(jìn)行new,然后調(diào)用構(gòu)造函數(shù),如果構(gòu)造出現(xiàn)異常,就需要delete,否則內(nèi)存泄漏。如果用了placement new,那么編譯器會(huì)尋找含有同樣參數(shù)的placement delete,否則不會(huì)delete,因此必須成對(duì)寫(xiě)接收同樣參數(shù)的placement new和placement delete。同時(shí),為了讓用戶(hù)主動(dòng)使用delete時(shí)能進(jìn)行正確操作,你需要同時(shí)定義一個(gè)普通形式的delete,來(lái)執(zhí)行和placement delete同樣的特殊實(shí)現(xiàn)。你在類(lèi)中聲明placement new后,會(huì)掩蓋C++提供的new函數(shù),因此除非你確實(shí)不想用戶(hù)使用默認(rèn)的new,否則你需要確保它們還可用(條款33)。
雜項(xiàng)討論
條款53:不要輕忽編譯器的警告
對(duì)于編譯器編譯時(shí)給出的警告信息,最好立即修復(fù),避免后續(xù)調(diào)試半天來(lái)尋找編譯器早就告知你的問(wèn)題。
條款54:讓自己熟悉包括TR1在內(nèi)的標(biāo)準(zhǔn)程序庫(kù)
C++98的標(biāo)準(zhǔn)程序庫(kù)有:
而TR1是新的一系列組件,在std內(nèi)的tr1命名空間中,比如:std::tr1::shared_ptr。它包含:
條款55:讓自己熟悉Boost
Boost是一個(gè)程序庫(kù),其由C++標(biāo)準(zhǔn)委員會(huì)成員創(chuàng)設(shè),可視為一個(gè)“可被加入標(biāo)準(zhǔn)C++的各種功能”的測(cè)試場(chǎng),涵蓋眾多經(jīng)過(guò)多輪復(fù)核的優(yōu)質(zhì)程序,如果想知道當(dāng)前C++最高技術(shù)水平、想一瞥未來(lái)C++的可能長(zhǎng)相?看看Boost[https://boost.org]吧。
- 在調(diào)用動(dòng)作前加上“this->”
- 使用using聲明式來(lái)在子類(lèi)中聲明基類(lèi)的該接口
- 明確指出被調(diào)用的函數(shù)位于基類(lèi):Base::xxx();
- 以上做法都是承諾被調(diào)用的函數(shù)一定會(huì)在各種特化基類(lèi)中均聲明。如果沒(méi)有聲明,還是會(huì)在運(yùn)行期報(bào)錯(cuò)。
- 因非類(lèi)型模板參數(shù)造成的代碼膨脹(比如用尺寸做模板參數(shù)導(dǎo)致為不同尺寸的同一對(duì)象生成多份相同代碼),往往可消除,做法是將該參數(shù)改為函數(shù)參數(shù)或者類(lèi)成員變量,而不要放到模板的參數(shù)中。
- 因類(lèi)型參數(shù)造成的代碼膨脹(比如int和long有相同的二進(jìn)制表述,但作為模板參數(shù)會(huì)產(chǎn)生兩份代碼),往往可降低,做法是讓帶有完全相同二進(jìn)制表述的具體類(lèi)型共享實(shí)現(xiàn)碼——使用唯一一份底層實(shí)現(xiàn)。
- STL
- Iostreams,包括cin、cout、cerr、clog等
- 國(guó)際化支持
- 數(shù)值處理
- 異常階層體系
- C89標(biāo)準(zhǔn)程序庫(kù)
- 智能指針,包括shared_ptr和weak_ptr。
- function:支持以函數(shù)簽名(出參類(lèi)型+入?yún)㈩?lèi)型)作為模板
- bind:綁定器
- 無(wú)序hash表,用以實(shí)現(xiàn)無(wú)序的set、multiset、map、multimap
- 正則表達(dá)式
- tuples:擴(kuò)充pair,能持有任意個(gè)數(shù)的對(duì)象,類(lèi)似python中的tuples。
- array:STL化的數(shù)組,支持begin和end,不過(guò)其大小固定,不適用動(dòng)態(tài)內(nèi)存。
- mem_fn
- reference_wrapper:讓引用的行為更像對(duì)象,可以被容器持有。
- 隨機(jī)數(shù)生成工具:大大超越rand
- 數(shù)學(xué)特殊函數(shù):多種數(shù)學(xué)函數(shù)
- C99兼容擴(kuò)充。
- type traits,使用見(jiàn)條款47,提供類(lèi)型的編譯期信息。
- result_of:是個(gè)模板,用來(lái)推到函數(shù)調(diào)用的返回類(lèi)型。
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)串的順序存儲(chǔ)表示與基本操作
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)串的順序存儲(chǔ)表示與基本操作,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
C++實(shí)現(xiàn)一個(gè)簡(jiǎn)單的SOAP客戶(hù)端
這篇文章主要介紹了C++實(shí)現(xiàn)一個(gè)簡(jiǎn)單的SOAP客戶(hù)端,在C++中,一般使用gSOAP來(lái)實(shí)現(xiàn)客戶(hù)端、服務(wù)端,下面一起進(jìn)入文章了解具體內(nèi)容,需要的朋友可以參考一下2021-11-11
C語(yǔ)言實(shí)現(xiàn)帶頭雙向循環(huán)鏈表的接口
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)帶頭雙向循環(huán)鏈表的接口,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10
C語(yǔ)言實(shí)現(xiàn)繪制貝塞爾曲線(xiàn)的函數(shù)
貝塞爾曲線(xiàn),又稱(chēng)貝茲曲線(xiàn)或貝濟(jì)埃曲線(xiàn),是應(yīng)用于二維圖形應(yīng)用程序的數(shù)學(xué)曲線(xiàn)。本文將利用C語(yǔ)言實(shí)現(xiàn)繪制貝塞爾曲線(xiàn)的函數(shù),需要的可以參考一下2022-12-12
C++實(shí)現(xiàn)基于reactor的百萬(wàn)級(jí)并發(fā)服務(wù)器
本文介紹了基于Reactor模式的百萬(wàn)級(jí)并發(fā)服務(wù)器,使用epoll進(jìn)行高效I/O多路復(fù)用,支持多個(gè)端口的監(jiān)聽(tīng),并通過(guò)回調(diào)機(jī)制處理每個(gè)連接的接收和發(fā)送操作,需要的朋友可以參考下2025-02-02

