1,C++屬于一個(gè)語言聯(lián)邦 : C Object-Oriented C++ Template C++ STL
2,應(yīng)盡量以const,enum,inline替換#define
如果一個(gè)常量是class專屬常量又是static,且為整數(shù)類型(int,char,bool),則需特殊處理:只要不取它們的地址,可以聲明并使用它們而不須提供定義式。
但是如果取某個(gè)class專屬常量的地址,或縱使不取其地址而編譯器卻(不正確的)堅(jiān)持要看到一個(gè)定義式,必須提供定義式
enum的行為某方面比較像#define而不像const:取一個(gè)const地址合法,但是取一個(gè)enum地址就不合法,而取#define也不合法,如果不想讓被人獲得一個(gè)pointer或reference指向你的某個(gè)整數(shù)常量,enum可以幫助你實(shí)現(xiàn)這個(gè)約束
#define和enum不會(huì)設(shè)定額外的空間,const在優(yōu)秀的編譯器中也許可能也是這樣,但是不夠優(yōu)秀的編譯器就必須設(shè)定額外的空間
3,盡可能使用const
const語法雖然變化多端,但并不莫測(cè)高深。如果關(guān)鍵字const出現(xiàn)在*左側(cè),表示被指物是常量;如果出現(xiàn)在*右邊,表示指針自身是常量;如果出現(xiàn)在*兩邊,表示被指物和指針都是常量
std::vector<int> vec;
const std::vector<int>::interator iter = vec.begin() T*const
std::vector<int>:const_iterator iter = vec.begin() const T*
將const實(shí)施于成員函數(shù)的目的,是為了確認(rèn)該成員函數(shù)可作用于const對(duì)象身上
const特性的兩個(gè)函數(shù)可以重載(必須在類中,不在類中會(huì)出現(xiàn)問題) const函數(shù)不可改變對(duì)象任何變量(注意區(qū)分指針的情況,只要指針不變,指向的值可變化)
注意操作符[]的重載要返回值的引用,否則不能對(duì)結(jié)果賦值(相當(dāng)于對(duì)值的一個(gè)拷貝賦值,沒有意義)
mutable可以在常函數(shù)中修改其值,但是一般作用是:1,用于緩存2,或必須在常函數(shù)中修改值
利用const Operator[]實(shí)現(xiàn)出non-const版本:
class TextBlock {
public:
const char & operator[](std::size_t position) const { ..... return text[position]; }
char & operator[](std::size_t position){
return const_cast<char &>( static_cast<const TextBlock&> (*this)[position]);
};
非const調(diào)用const是安全的
const調(diào)用非const是不安全的,(因?yàn)榉莄onst有可能改變)
總結(jié):
1>將某些東西聲明為const可幫助編譯器偵測(cè)出錯(cuò)誤用法。const可被施加于任何作用域內(nèi)的對(duì)象、函數(shù)參數(shù)、函數(shù)返回類型、成員函數(shù)本體
2>編譯器強(qiáng)制實(shí)施bitwise constness,但你編寫程序時(shí)應(yīng)該使用“概念上的常量性”(conceptual constness)
3>當(dāng)const和non-const成員函數(shù)有著實(shí)質(zhì)等價(jià)的實(shí)現(xiàn)時(shí),令non-const版本調(diào)用const版本可避免代碼重復(fù)
4,確定對(duì)象被使用之前已被初始化:
為了保證一定性,使用內(nèi)建數(shù)據(jù)類型一定要初始化,構(gòu)造對(duì)象要保證構(gòu)造函數(shù)初始化它的成員
C++ 對(duì)“定義在不同的編譯單元內(nèi)的non-local static對(duì)象”的初始化相對(duì)次序并無明確定義。因?yàn)闆Q定它們初始化次序相當(dāng)困難,根本無解
最常用的形式,就是 多個(gè)編譯單元內(nèi)的non-local static對(duì)象經(jīng)由“模板隱式具體化,implicit template instantiations"形式(而后者自己可能也是經(jīng)由”模板隱式具現(xiàn)化“形成)
任何一種non-const static對(duì)象,不論它是local或non-local,在多線程環(huán)境下“等待某事發(fā)生”都會(huì)有麻煩。處理方法:在程序的單線程啟動(dòng)階段手工調(diào)用所有的reference-returning函數(shù),這可消除與初始化有關(guān)的“競(jìng)速形勢(shì)”
為了避免在對(duì)象初始化之前過早地使用它們,你需要做三件事:
第一,手工初始化內(nèi)置型non-member對(duì)象。
第二,使用成員初值列對(duì)付對(duì)象的所有成分
第三,在”初始化次序不缺定性“(這對(duì)不同編譯單元所定義的non-local static對(duì)象是一種折磨)氛圍下加強(qiáng)設(shè)計(jì)
總結(jié):
1>為內(nèi)置對(duì)象進(jìn)行手工初始化,因?yàn)镃++不保證初始化它們
2>構(gòu)造函數(shù)最好使用成員初始化列表,而不要在構(gòu)造函數(shù)體內(nèi)使用賦值操作。次序和變量聲明的順序一致
3>為免除“跨編譯單元之初始化次序”問題,請(qǐng)以local static對(duì)象替換non-local static對(duì)象
5,編譯器默認(rèn)提供四個(gè)public且inline函數(shù),賦值,拷貝,構(gòu)造,析構(gòu),但是它們是在需要的時(shí)候才被編譯器創(chuàng)建出來的
C++不允許讓引用改指向不同的對(duì)象
>如果打算在一個(gè)“內(nèi)含reference成員”的class內(nèi)支持賦值操作,必須自己定義copy assignment操作符
>如果某個(gè)base class將copy assignment操作符聲明為PRivate,編譯器將拒絕為其derived classes 生成copy assignment操作符
6,若不想使用編譯器自動(dòng)生成的函數(shù),就該明確拒絕:將他們聲明為private,同時(shí)不要實(shí)現(xiàn)他們,這樣的話即使friend或者成員函數(shù)調(diào)用他們連接器會(huì)提示錯(cuò)誤
另外一種方法是把他們聲明為private在一個(gè)單獨(dú)的類中,然后用這個(gè)類繼承它
7,為多態(tài)基類聲明virtual析構(gòu)函數(shù)
>帶有多態(tài)性質(zhì)的base classes應(yīng)該聲明一個(gè)virtual析構(gòu)函數(shù)。如果class帶有任何virtual函數(shù),他就應(yīng)該擁有一個(gè)virtual析構(gòu)函數(shù)
>classes的設(shè)計(jì)目的如果不是作為base classes使用,或不是為了具備多態(tài)性,就不該聲明virtual析構(gòu)函數(shù)
8,別讓異常逃離析構(gòu)函數(shù)
>析構(gòu)函數(shù)絕對(duì)不要吐出異常。如果一個(gè)被析構(gòu)函數(shù)調(diào)用的函數(shù)可能拋出異常,析構(gòu)函數(shù)應(yīng)該捕捉任何異常,然后吞下它們(不傳播)或結(jié)束程序
>如果客戶需要對(duì)某個(gè)操作函數(shù)運(yùn)行期間拋出異常作出反應(yīng),那么class應(yīng)該提供一個(gè)普通函數(shù)(而非在析構(gòu)函數(shù)中)執(zhí)行該操作
9,絕不在構(gòu)造和析構(gòu)過程中調(diào)用virtual函數(shù)
>無法使用virtual函數(shù)從base classes向下調(diào)用,在構(gòu)造期間,可以藉由“令derived classes將必要的構(gòu)造信息向上傳遞到base class構(gòu)造函數(shù)”替換加以彌補(bǔ)
>在構(gòu)造和析構(gòu)期間不要調(diào)用virtual函數(shù),因?yàn)檫@類調(diào)用從不下降至derived class(比起當(dāng)前執(zhí)行構(gòu)造函數(shù)和析構(gòu)函數(shù)的那層)
10,令operator=返回一個(gè) reference to *this
目的是:為了實(shí)現(xiàn)“連鎖賦值”
11,在operator=中處理“自我賦值”
自我賦值 發(fā)生在對(duì)象被賦值給自己時(shí)
>確保當(dāng)對(duì)象自我賦值時(shí)operator=有良好行為。其中技術(shù)包括比較“來源對(duì)象”和“目標(biāo)對(duì)象”的地址、精心周到的語句順序、以及copy-and-swap
>確定任何函數(shù)如果操作一個(gè)以上的對(duì)象,而其中多個(gè)對(duì)象是同一對(duì)象時(shí),其行為仍然正確
12,復(fù)制對(duì)象時(shí)勿忘其每一個(gè)成分
自己編寫copying函數(shù),要確保:
1,復(fù)制所有l(wèi)ocal成員變量
2,調(diào)用所有base classes內(nèi)的適當(dāng)copying函數(shù)
>Copying函數(shù)應(yīng)該確保復(fù)制“對(duì)象內(nèi)的所有成員變量”及“所有base class成分”
>不要嘗試以某個(gè)copying函數(shù)實(shí)現(xiàn)另一個(gè)copying函數(shù)。應(yīng)該將共同機(jī)能放進(jìn)第三個(gè)函數(shù)中,并由兩個(gè)copying函數(shù)共同調(diào)用
所謂資源就是:一旦用了它,將來必須還給系統(tǒng),否則會(huì)出現(xiàn)問題。C++最常用的資源就是動(dòng)態(tài)內(nèi)存分配,當(dāng)然還有其他資源:文件描述扶,互斥鎖,圖形界面中的字型和筆刷、數(shù)據(jù)庫(kù)連接、以及網(wǎng)絡(luò)sockets
13,以對(duì)象管理資源
兩個(gè)關(guān)鍵想法:
>獲得資源后立刻放進(jìn)管理對(duì)象內(nèi)
>管理對(duì)象運(yùn)用析構(gòu)函數(shù)確保資源被釋放
由于auto_ptr被銷毀時(shí)會(huì)自動(dòng)刪除它所指之物,所以一定要注意別讓多個(gè)auto_ptr同時(shí)指向同一對(duì)象。如果真是那樣,對(duì)象會(huì)被刪除一次以上,而那會(huì)使你的程序搭上駛向“未定義行為”的快速列車上。為了預(yù)防這個(gè)問題,auto_ptr有一個(gè)不尋常的性質(zhì):若通過copy構(gòu)造函數(shù)或copy assignment操作符復(fù)制它們,它們會(huì)變成null,而復(fù)制所得的指針將取的資源的唯一控制權(quán)!!!(該性質(zhì)有利也有弊)
auto_ptr的替代方案是“引用計(jì)數(shù)型智慧指針”(RCSP),RCSP也是個(gè)智能指針,持續(xù)追蹤共有多少對(duì)象指向某筆資源,并在無人指向它時(shí)自動(dòng)刪除該資源。RCSP提供的行為類似垃圾回收,不同的是RCSP無法打破環(huán)狀引用:::tr1:shared_ptr就是一個(gè)RCSP
注意:auto_ptr和tr1::shared_ptr兩者都在析構(gòu)函數(shù)內(nèi)做delete而不是delete[]動(dòng)作!!如果對(duì)數(shù)組指針調(diào)用則是餿主意
>為了防止資源泄漏,請(qǐng)使用RAII對(duì)象,它們?cè)跇?gòu)造函數(shù)中活得資源并在析構(gòu)函數(shù)中釋放資源 (Resource Acquisition Is Initialization,RAII)
>兩個(gè)常被使用的RAII classes分別是tr1::shared_ptr和auto_ptr。前者通常是較佳選擇,已經(jīng)其copy行為比較直觀。若選擇auto_ptr,復(fù)制動(dòng)作會(huì)時(shí)它(被復(fù)制物)指向null
14,在資源管理類中小心copying行為
在RAII中當(dāng)一個(gè)對(duì)象被復(fù)制時(shí),出現(xiàn)嚴(yán)重后果,一般有兩種方案:
>禁止復(fù)制(聲明為private)
>對(duì)底層資源祭出“引用計(jì)數(shù)法”(reference-count) 這種做法一般如果內(nèi)含有一個(gè)tr1::shared_ptr ,但是默認(rèn)事件是count為0時(shí)刪除,幸運(yùn)的是我們可以自己指定為0時(shí)的事件
類的析構(gòu)函數(shù)會(huì)自動(dòng)調(diào)用其non-static成員變量的析構(gòu)函數(shù)
復(fù)制底部資源 (深度拷貝)
轉(zhuǎn)移底部資源的擁有權(quán) (auto_ptr)
15,在資源管理類中提供對(duì)原始資源的訪問
tr1::shared_ptr和auto_ptr都提供了一個(gè)get函數(shù),用來執(zhí)行顯式轉(zhuǎn)換,也就是它返回智能指針內(nèi)部的原始指針,以便于直接訪問時(shí)可以通過
它們還重載了(->和*)操作符
>APIs往往要求訪問原始資源(raw resources),所以每一個(gè)RAII class應(yīng)該提供一個(gè)“取得其所管理之資源”的辦法
>對(duì)原始資源的訪問可能經(jīng)由顯式轉(zhuǎn)換或隱式轉(zhuǎn)換。一般而言顯式轉(zhuǎn)換比較安全,但隱式轉(zhuǎn)換對(duì)客戶比較方便
16,成對(duì)使用new和delete時(shí)要采用相同形式
最好不要用typedef,否則會(huì)造成語義不清:
typedef std::string AddressLines[4];
std::string *pal = new AddressLines;
delete pal; 錯(cuò)誤!!!
delete[] pal ; 正確!!!
17,以獨(dú)立語句將newed對(duì)象置入智能指針
> 以獨(dú)立語句將newed對(duì)象存儲(chǔ)于(置入)智能指針內(nèi)。如果不這樣做,一旦異常被拋出,有可能導(dǎo)致難以覺察的資源泄露
18,讓接口容易被正確使用,不易被誤用
>好的接口很容易被正確的使用,不容易被誤用。應(yīng)該在所有的接口中努力達(dá)成這些性質(zhì)
>“促進(jìn)正確使用”的辦法包括接口一致性,以及與內(nèi)置類型的行為兼容
>“組織誤用”的辦法包括建立新類型、限制類型上的操作,束縛對(duì)象值,以及消除客戶的資源管理責(zé)任
>tr1::shared_ptr支持定制型刪除器。這個(gè)防范DLL問題,可被用來自動(dòng)解除互斥鎖等等
19,設(shè)計(jì)class猶如設(shè)計(jì)type
好的types是一項(xiàng)艱巨的工作。好的types有自然的語法,直觀的語義,以及一或多個(gè)高效的實(shí)現(xiàn)品
如何設(shè)計(jì)好的classes:
>新的type的對(duì)象應(yīng)該如何被創(chuàng)建和銷毀
>對(duì)象的初始化和對(duì)象的賦值該有什么差別
>新的type的對(duì)象如果被passed by value(以值傳遞),意味著什么
>什么是新type的“合法值”
>新的type需要配合某個(gè)繼承圖系么
>新的type需要什么樣的轉(zhuǎn)換
>什么樣的操作符和函數(shù)對(duì)此新type而言是合理的
>什么樣的標(biāo)準(zhǔn)函數(shù)應(yīng)該駁回
>誰該取用新type的成員
>什么是新type的“未聲明接口”
>新type有多么一般化
>真的需要一個(gè)新type么
20,寧以pass-by-reference-to-const替換pass-by-value
>盡量以pass-by-reference-to-const替換pass-by-value。前者通常比較高效,并可避免切割問題
>以上規(guī)則并不適用于內(nèi)置類型,以及STL的迭代器和函數(shù)對(duì)象。對(duì)它們而言,pass-by-value往往比較適當(dāng)
21,必須返回對(duì)象時(shí),別妄想返回其reference
任何時(shí)候看到一個(gè)reference聲明式,都應(yīng)該立刻問自己,它的另一個(gè)名稱是什么?因?yàn)樗欢ㄊ悄澄锏牧硪粋€(gè)名稱
任何函數(shù)如果返回一個(gè)reference指向某個(gè)local對(duì)象,都將一敗糊涂
>絕不要返回point或reference指向一個(gè)local stack對(duì)象,或返回reference指向一個(gè)heap-allocated對(duì)象,或返回pointer或reference指向一個(gè)local static對(duì)象而有可能同時(shí)需要多個(gè)這樣的對(duì)象
22,將成員變量聲明為private
>切記將成員函數(shù)聲明為private。這可賦予客戶訪問數(shù)據(jù)的一致性、可細(xì)微劃分訪問控制、允許約束條件獲得保證,并提供class作者以充分的實(shí)現(xiàn)彈性
>protected并不比public更具封裝性
23,寧以non-menber、non-friend替換menber函數(shù)
這樣做可以增加封裝性、包裹彈性和機(jī)能擴(kuò)充性
24,若所有參數(shù)皆需要類型轉(zhuǎn)換,請(qǐng)為此采用non-member函數(shù)
>如果需要為某個(gè)函數(shù)的所有參數(shù)(包括被this指針?biāo)傅哪莻€(gè)隱喻參數(shù))進(jìn)行類型轉(zhuǎn)換,那么這個(gè)函數(shù)必須是個(gè)non-member
25,考慮一個(gè)不拋出異常的swap函數(shù)
C++只允許對(duì)class template偏特化,在function templates身上偏特化行不通,解決方案是:為它添加一個(gè)重載版本
>當(dāng)std::swap對(duì)你的類型效率不高時(shí),提供一個(gè)swap成員函數(shù),并確定 這個(gè)函數(shù)不拋出異常
>如果你提供一個(gè)member swap,也該提供一個(gè)non-member swap用來調(diào)用前者。對(duì)于classes(而非templates),也請(qǐng)?zhí)鼗痵td::swap
>調(diào)用swap時(shí)應(yīng)針對(duì)std::swap使用using聲明式,然后調(diào)用swap并且不帶任何“命名空間資格修飾”
>為“用戶定義類型”進(jìn)行std template全特化是好的,但千萬不要嘗試在std內(nèi)加入某些對(duì)std而言全新的東西
26,盡可能延后變量定義式的出現(xiàn)時(shí)間
>盡可能延后變量定義式的出現(xiàn)。這樣做可增加程序的清晰度并改善程序的效率。
27,盡量少做轉(zhuǎn)型動(dòng)作
>const_cast通常被用來將對(duì)象的常量性轉(zhuǎn)除。它也是唯一有此能力的C++-style轉(zhuǎn)型操作符
>dynamic_cast主要用來執(zhí)行“安全向下轉(zhuǎn)型”,也就是用來決定某對(duì)象是否歸屬繼承體系中的某個(gè)對(duì)象。它是唯一無法由舊式語法執(zhí)行的動(dòng)作,也是唯一可能耗費(fèi)重大運(yùn)行成本的轉(zhuǎn)型動(dòng)作
>reinterpret_cast意圖執(zhí)行低級(jí)轉(zhuǎn)型,實(shí)際動(dòng)作(及結(jié)果)可能取決于編譯器,這也就是表示它不可移植。例如將point to int轉(zhuǎn)型為int
>static_cast用來強(qiáng)迫飲食轉(zhuǎn)換。例如將non-const轉(zhuǎn)型const對(duì)象,或?qū)nt轉(zhuǎn)型double等等。但是無法將const轉(zhuǎn)型non-const
任何一個(gè)類型轉(zhuǎn)換(不論是通過轉(zhuǎn)型操作而進(jìn)行的顯式轉(zhuǎn)換,或通過編譯器完成的隱式轉(zhuǎn)換)往往真的令編譯器編譯出運(yùn)行期執(zhí)行的碼
單一對(duì)象(例如Derived對(duì)象)可能擁有一個(gè)以上的地址(例如“以base *指向它”時(shí)的地址和“以Derived *指向它”時(shí)的地址)行為
之所以需要dynamic_cast,通常是因?yàn)橄朐谝粋€(gè)認(rèn)定為derived對(duì)象身上執(zhí)行derived class操作函數(shù),但手上卻只有一個(gè)"指向base”的pointer或reference,只能靠它們呢來處理對(duì)象
絕對(duì)必須避免的一件事是所謂的"連串 dynamic_casts"
>如果可以,盡量避免轉(zhuǎn)型,特別是在注重效率的代碼中避免dynamic_casts。如果有個(gè)設(shè)計(jì)需要轉(zhuǎn)型這個(gè)動(dòng)作,試著發(fā)展無需轉(zhuǎn)型的替代品
>如果轉(zhuǎn)型是必要的,試著將它隱藏于某個(gè)函數(shù)背后。客戶隨后可以調(diào)用該函數(shù),而不需要將轉(zhuǎn)型放進(jìn)他們自己的代碼內(nèi)
>寧可使用C++-style轉(zhuǎn)型,不要使用舊式轉(zhuǎn)型。前者容易辨識(shí)出來,而且也比較有著分門別類的職掌
28,避免返回handles指向?qū)ο髢?nèi)部成分
兩個(gè)教訓(xùn):
--->成員變量的封裝性最多只等于"返回其reference"的函數(shù)的訪問級(jí)別
--->如果const成員函數(shù)傳出一個(gè)reference,后者所指數(shù)據(jù)與對(duì)象自身關(guān)聯(lián),而它又被存儲(chǔ)于對(duì)象之外,那么這個(gè)函數(shù)的調(diào)用者可以修改那筆數(shù)據(jù)
handles: 指針,reference,迭代器
避免返回handles(reference,pointer,iterator)指向?qū)ο髢?nèi)部。遵守這個(gè)約定可增加封裝性,幫助const成員函數(shù)的行為像個(gè)const,并將發(fā)生”虛吊號(hào)碼牌“(dangling handles)的可能性降到最低
29,為"異常安全"而努力是值得的
當(dāng)異常被拋出時(shí),帶有異常安全性的函數(shù)會(huì):
=>不泄露任何資源
=>不允許數(shù)據(jù)敗壞
異常安全函數(shù)提供以下三個(gè)保證之一:
==>基本承諾
==>強(qiáng)烈保證
==>不拋擲保證
1,> 異常安全函數(shù)(Exception-safe functions)即使發(fā)生異常也不會(huì)泄露資源或允許任何數(shù)據(jù)結(jié)構(gòu)敗壞。這樣的函數(shù)區(qū)分三種可能的保證:基本型,強(qiáng)烈型,不拋異常型
2,>“強(qiáng)烈保證"往往能夠以copy-and-swap實(shí)現(xiàn)出來,但"強(qiáng)烈保證"并非對(duì)所有函數(shù)都可實(shí)現(xiàn)或具備實(shí)現(xiàn)意義
3,>函數(shù)提供的"異常安全保證"通常最高只等于其所調(diào)用之各個(gè)函數(shù)的"異常安全保證"中的最弱者
30,透徹了解inlining的里里外外
inline只是對(duì)編譯器的一個(gè)申請(qǐng),不是強(qiáng)制命令
inline函數(shù)通常 一定 被置于頭文件內(nèi)
inline 在大多數(shù)C++程序中時(shí)編譯器行為,只有少數(shù)基于.NET CLI(Common Language Infrastructure,公共語言基礎(chǔ)設(shè)施)可以在運(yùn)行期完成inlining
Template通常也被置于頭文件中,以為一旦被使用,編譯器為了將它具現(xiàn)化,需要知道它長(zhǎng)什么樣子(像inline),(并不是同一準(zhǔn)則,某些建置環(huán)境可以在連接期執(zhí)行template具現(xiàn)化)
編譯器拒絕將過于復(fù)雜的函數(shù)(循環(huán)或遞歸)的函數(shù)inlining。而所有對(duì)virtual函數(shù)的調(diào)用(除非最平淡無奇的)也對(duì)會(huì)使inling落空,因?yàn)関irtual是運(yùn)行期確定
編譯器通常不對(duì)“通過函數(shù)指針而進(jìn)行的調(diào)用”實(shí)施inlining,因?yàn)橐_定地址
inline函數(shù)無法升級(jí)
很多調(diào)試器對(duì)inline函數(shù)束手無策
>將大多數(shù)inlining限制在小型、被頻繁調(diào)用的函數(shù)身上。這可使日后的調(diào)試過程和二進(jìn)制升級(jí)更容易,也可使?jié)撛诘拇a膨脹問題最小化,使程序的速度提升機(jī)會(huì)最大化
>不要只因?yàn)閒unction templates出現(xiàn)在頭文件,就將它們聲明為inline
31,將文件間的編譯依存關(guān)系降至最低
如果使用object references 或 object pointers可以完成任務(wù),就不要使用objects
如果能夠,盡量以class聲明替換class定義式
為聲明式和定義式提供不同的頭文件
>支持“編譯依存性最小化”的一般構(gòu)想是:相依于聲明式,不要 相依于定義式。基于此構(gòu)想的兩個(gè)手段是Handle classes和Interface classes
>程序庫(kù)頭文件應(yīng)該以“完全且僅有聲明式”的形式存在。這種做法不論是否涉及templates都適用
繼承與面向?qū)ο?/p>
32,確定public繼承塑模出is-a關(guān)系
以C++進(jìn)行面向?qū)ο缶幊蹋钜囊粋€(gè)原則:公開繼承 意味 "is-a“的關(guān)系
企鵝是一種鳥,但企鵝不會(huì)飛 理論解決辦法:1,雙繼承 2,重新(一個(gè)錯(cuò)誤的)飛功能 3,都不寫飛功能
>"public"繼承意味著is-a。適用于base classes身上的每一件事情一定也適用于derived classes身上,因?yàn)槊恳粋€(gè)derived class對(duì)象也都是一個(gè)
base class對(duì)象
33,避免遮掩繼承而來的名稱
1 #include <iostream> 2 using namespace std; 3 4 class Base { 5 private: 6 int x; 7 public: 8 virtual void mf1() = 0; 9 virtual void mf1(int);10 virtual void mf2();11 void mf3();12 void mf3(double);13 };14 15 class Derived : public Base {16 public:17 virtual void mf1();18 void mf3();19 void mf4();20 };21 22 int main()23 {24 Derived d;25 int x;26 27 d.mf1(); //Derived::mf128 d.mf1(x); //ERROR !!! NO ARGS using Base::mf129 d.mf2(); //Base::mf230 d.mf3(); //Derived::mf331 d.mf3(x); //ERROR !!! NO ARGS using Base::mf332 33 return 0;34 }
上面的程序只是為了說明編譯時(shí)候的正確性,因此有些函數(shù)沒有實(shí)現(xiàn),鏈接肯定通不過,不過能說明問題
>derived classes內(nèi)的名稱會(huì)遮掩base classes內(nèi)的名稱。在public繼承下從來沒有人希望如此
>為了讓被遮掩的名稱再見天日,可使用using聲明式或轉(zhuǎn)交函數(shù)
34,區(qū)分接口繼承和實(shí)現(xiàn)繼承
聲明一個(gè)pure virtual函數(shù)的目的是為了讓derived classes只繼承函數(shù)接口
聲明簡(jiǎn)樸的(非純)impure virtual函數(shù)的目的,是讓derived classes繼承該函數(shù)的接口和缺省實(shí)現(xiàn)
聲明non-virtual函數(shù)的目的是為了令derived classes繼承函數(shù)的接口及一份強(qiáng)制性實(shí)現(xiàn)
一個(gè)典型的程序有80%的執(zhí)行時(shí)間花費(fèi)在%20的代碼身上
>接口繼承和實(shí)現(xiàn)繼承不同。在public繼承之下,derived classes總是繼承base class的接口
>pure virtual函數(shù)只具體指定接口繼承
>簡(jiǎn)樸的(非純)impure virtual函數(shù)具體指定接口繼承及缺省實(shí)現(xiàn)繼承
>non-virtual函數(shù)具體指定接口繼承以及強(qiáng)制性實(shí)現(xiàn)繼承
35,考慮virutal 函數(shù)以外的其他選擇
>virtual函數(shù)的替代方案包括NVI手法及Stratrgy設(shè)計(jì)模式的多種形式。NVI手法自身是一個(gè)特殊形式的Template Method設(shè)計(jì)模式
>將機(jī)能從成員函數(shù)移到class外部函數(shù),帶來的一個(gè)缺點(diǎn)是,非成員函數(shù)無法訪問class的non-public成員
>tr1::function對(duì)象的行為就像一般函數(shù)指針。這樣的對(duì)象可接納“與給定值目標(biāo)簽名兼容”的所有可調(diào)用物
36,絕不重新定義繼承而來的non-virtual函數(shù)
>絕對(duì)不要重新定義繼承而來的non-virtual
37,絕不重新定義繼承而來的缺省參數(shù)值
virtual函數(shù)系動(dòng)態(tài)綁定,而缺省參數(shù)值卻是靜態(tài)綁定
>絕對(duì)不要重新定義一個(gè)繼承而來的缺省參數(shù)值,因?yàn)槿笔?shù)值都是靜態(tài)綁定,而virtual函數(shù)—你唯一應(yīng)該覆寫的東西——確實(shí)動(dòng)態(tài)綁定
38,通過復(fù)合塑模出has-a或“根據(jù)某物實(shí)現(xiàn)出”
>復(fù)合的意義和public繼承完全不同
>在應(yīng)用域,復(fù)合意味has-a。在實(shí)現(xiàn)域,復(fù)合意味is-implemented-in-terms-of(根據(jù)某物實(shí)現(xiàn)出)
39,明智而審慎的使用private繼承
private繼承主要用于“當(dāng)一個(gè)意欲成為derived class者想訪問一個(gè)意欲成為base class者的protected成分,或?yàn)榱酥匦露x一或多個(gè)virtual函數(shù)
EBO empty base optimization 空白基類最優(yōu)化,只適用于單一繼承
復(fù)合和private繼承都意味 is-implemented-in-terms-of
>Private 繼承意味is-implemented-in-terms-of(根據(jù)某物實(shí)現(xiàn)出)。它通常比復(fù)合的級(jí)別低。但是當(dāng)derived class需要訪問protected base class的成員,或需要重新定義繼承而來的virtual函數(shù)時(shí),這么設(shè)計(jì)是合理的
>和復(fù)合不同,private繼承可以造成empty base最優(yōu)化。這對(duì)致力于“對(duì)象尺寸最小化”的程序庫(kù)開發(fā)者而言,可能很重要
40,明智而審慎的使用多重繼承
C++編譯器解析重載函數(shù)調(diào)用的規(guī)則:在看到是否有個(gè)函數(shù)可取用之前,C++首先確認(rèn)這個(gè)函數(shù)對(duì)此調(diào)用的最佳匹配。找出最佳匹配函數(shù)后才檢驗(yàn)其可取用性
virtual base的初始化責(zé)任是由繼承體系中的最低層class 負(fù)責(zé)。這暗示:
1>classes若派生自virtual bases而需要初始化,必須認(rèn)知其virtual bases——不論那些bases距離多遠(yuǎn)
2>當(dāng)一個(gè)新的derived class加入繼承體系中,它必須承擔(dān)起virtual bases(不論直接還是間接)的初始化責(zé)任
對(duì)待virtual base classes: 1,非必須不是有2,如果必須使用,盡可能避免在其中放置數(shù)據(jù)
>多重繼承比單一繼承復(fù)雜。它可能導(dǎo)致新的歧義性,以及對(duì)virtual繼承的需要
>virtual繼承會(huì)增加大小,速度,初始化(及賦值)復(fù)雜度等等成本。如果virtual base classes不帶任何數(shù)據(jù),將是最具實(shí)用價(jià)值的情況
>多重繼承的確有正當(dāng)用途。其中一個(gè)情節(jié)涉及"public繼承某個(gè)Interface class"和"private 繼承某個(gè)協(xié)助實(shí)現(xiàn)的class"的兩相組合
41,了解隱式接口和編譯期多態(tài)
Templates及泛型編程的世界,與面向?qū)ο笥懈镜牟煌T诖耸澜缰酗@式接口和編譯期多態(tài)依然存在,但重要性降低。反倒是隱式接口和編譯期多態(tài)移到前面了
>classes和templates都支持接口和多態(tài)
>對(duì)classes而言接口是顯式的,以函數(shù)簽名為中心。多態(tài)則是通過virtual函數(shù)發(fā)生于運(yùn)行期
>對(duì)template參數(shù)而言,接口是隱式的,奠基于有效表達(dá)式。多態(tài)則是通過template具現(xiàn)化和函數(shù)重載解析發(fā)生于編譯期
42,了解typename的雙重意義
template內(nèi)出現(xiàn)的名稱如果相依于某個(gè)template參數(shù),稱之為從屬名稱
如果C++解析器在template中遭遇一個(gè)嵌套從屬名稱,它便假設(shè)這個(gè)名稱不是個(gè)類型,除非告訴它是(typename)
typename不可以出現(xiàn)在base classes list內(nèi)的嵌套從屬類型名稱之前,也不可以在member initialization list中作為base class修飾符
>聲明template參數(shù)時(shí),前綴關(guān)鍵字class和typename可互換
>請(qǐng)使用關(guān)鍵字typename標(biāo)識(shí)嵌套從屬類型名稱;但不得在base class list(基類列)或member initialization list內(nèi)以它作為base class修飾符
43, 學(xué)習(xí)處理模板化基類內(nèi)的名稱
class 定義式最前面的"template<>"語法象征這既不是template也不是標(biāo)準(zhǔn)的class 。而是模板全特化
C++編譯期往往拒絕在templatized base classes(模板化基類)內(nèi)尋找繼承而來的名稱,因?yàn)橛锌赡芴鼗鴵碛胁煌慕涌冢】蛇@是個(gè)嚴(yán)重的問題,我們可以有三個(gè)辦法解決:
1,在調(diào)用base class函數(shù)動(dòng)作之前加上this->
2, 使用using聲明式
3, 明白指出被調(diào)用函數(shù)位于base class內(nèi),但是如果是虛函數(shù)的話會(huì)導(dǎo)致關(guān)閉虛函數(shù)"virtual 綁定行為"
>可在derived class tempalte內(nèi)通過"this->"指涉base class templates內(nèi)的成員名稱,或藉由一個(gè)明白的"base class資格修飾符"完成
44, 將于參數(shù)無關(guān)的代碼抽離templates
>Template生成多個(gè)classses和多個(gè)函數(shù),所以任何template代碼都不該與某個(gè)構(gòu)造成膨脹的template參數(shù)產(chǎn)生相依關(guān)系
>因非類型模板參數(shù)而造成的代碼膨脹,往往可消除,做法是以函數(shù)參數(shù)或class成員變量替換template參數(shù)
>因類型參數(shù)而造成的代碼膨脹,往往可降低,做法是讓帶有完全相同二進(jìn)制表述的具現(xiàn)類型共享實(shí)現(xiàn)碼
45,運(yùn)用成員函數(shù)模板接受所有兼容類型
>請(qǐng)使用member function templates生成"可接受所有兼容類型"的函數(shù)
>如果你聲明member templates用于"泛化copy構(gòu)造"或"飯或assignment操作",你還是需要聲明正常的copy構(gòu)造函數(shù)和copy assignment操作符
46, 需要類型轉(zhuǎn)換時(shí)請(qǐng)為模板定義非成員函數(shù)
在template實(shí)參推導(dǎo)過程中從不將隱式類型轉(zhuǎn)換函數(shù)納入考慮
>當(dāng)我們編寫一個(gè)class template,而它所提供之"與此template相關(guān)的"函數(shù)支持"所有參數(shù)之隱式類型轉(zhuǎn)換"時(shí),請(qǐng)將那些函數(shù)定義為"class template內(nèi)部的friend函數(shù)"
47, 請(qǐng)使用traits classes表現(xiàn)類型信息
>Traits classes使得"類型相關(guān)信息"在編譯期可用。它們以templates和"templates特化"完成實(shí)現(xiàn)
>整合重載技術(shù)后,traits classes有可能在編譯期對(duì)類型執(zhí)行if ... else 測(cè)試
48, 認(rèn)識(shí)template元編程
1 #include <iostream> 2 3 template<unsigned n> 4 struct Factorial 5 { 6 enum { value = n * Factorial<n-1>::value}; 7 }; 8 9 template<>10 struct Factorial<0> {11 enum { value = 1 };12 };13 14 15 int main(int argc,char *argv[])16 {17 std::cout << Factorial<5>::value << std::endl;18 std::cout << Factorial<10>::value << std::endl;19 20 return 0;21 }
好處:1,確保量度單位正確 2,優(yōu)化矩陣 3, 可以生成客戶定制之設(shè)計(jì)模式
>Template metaprograming可將工作由運(yùn)行期移往編譯期,因而得以實(shí)現(xiàn)早期錯(cuò)誤偵測(cè)和更高的執(zhí)行效率
>TMP可被用來生成"基于政策選擇組合"的客戶定制代碼,也可用來避免生成對(duì)某些特殊類型不適合的代碼
49,了解new-handler的行為
new-handler函數(shù)必須做以下事情:
1= 讓更多內(nèi)存可被使用
2= 安裝另一個(gè)new-handler
3= 卸除new-handler
4= 拋出bad_alloc(或派生自bad_alloc)的異常
5= 不返回
operator new要做以下事情:
1= 調(diào)用set_new_handler,告知Widget的錯(cuò)誤處理函數(shù)。這會(huì)將class的new-handler安裝為global new-handler
2= 調(diào)用global operator new,執(zhí)行實(shí)際之內(nèi)存分配。如果分配失敗,global operator new會(huì)調(diào)用class的new-handler,因?yàn)槟莻€(gè)函數(shù)才剛被安裝為global new-handler。如果global operator new最終無法分配足夠內(nèi)存,會(huì)拋出一個(gè)bad_alloc異常。在此情況下class的operator new必須恢復(fù)原來的global new-handler,然后再傳播該異常。為確保原來的new-handler總是能夠被重新安裝回去,class將global new-handler視為資源并遵守忠告,運(yùn)用資源管理對(duì)象防止資源泄漏
3= 如果global operator new能夠分配足夠一個(gè)class對(duì)象所用的內(nèi)存,class的operator new會(huì)返回一個(gè)指針,指向分配所得。class析構(gòu)函數(shù)會(huì)管理golbal new-handler,它會(huì)自動(dòng)將class ‘s operator new被調(diào)用前的那個(gè)global new-handler恢復(fù)回來
50,了解new和delete的合理替換機(jī)制
51,編寫new和delete時(shí)需固守常規(guī)
52,寫placement new 也要寫 placement delete
53,不要輕易忽略編譯期警告
54,讓自己熟悉包括TR1在內(nèi)的標(biāo)準(zhǔn)程序庫(kù)
55,讓自己熟悉Boost
有一種落差是,你配不上自己的野心,也辜負(fù)了所受的苦難
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注