C++對象的底層原理都在這兒了,還敢說學(xué)不會?

Part1一、關(guān)于對象
C 語言是程序性的,語言本身并沒有支持數(shù)據(jù)和函數(shù)之間的關(guān)聯(lián)性C++ 中可能采取抽象數(shù)據(jù)類型,或者是多層次的類結(jié)構(gòu)完成 C++ 的封裝并沒有增加多少成本,每一個成員函數(shù)雖然在class中聲明,但是卻不出現(xiàn)在每個對象中 每一個非內(nèi)聯(lián)的成員函數(shù)只會誕生一個函數(shù)實例 每個內(nèi)聯(lián)函數(shù)會在其每一個使用者身上產(chǎn)生一個函數(shù)實例
C++ 在布局以及存儲時間上主要的額外負擔(dān)是由virtual引起的 虛函數(shù)機制用以支持一個有效率的“執(zhí)行期綁定” 虛基類用來實現(xiàn)“多次出現(xiàn)在繼承關(guān)系中的基類,有一個單一而被共享的實例”
還有一些多重繼承下的額外負擔(dān),發(fā)生在一個派生類和其第二或后繼之基類的轉(zhuǎn)換之間
1.1 C++對象模式
C++對象模型有以下幾點非靜態(tài)數(shù)據(jù)成員放在類對象內(nèi)靜態(tài)數(shù)據(jù)成員放在類對象外靜態(tài)和非靜態(tài)成員函數(shù)也放在類對象外虛函數(shù)則不同 每個類中存放一個指針稱為vptr,指向虛函數(shù)表表中每個都指向一個虛函數(shù)

1.2 關(guān)鍵詞所帶來的差異
int ( *pq ) ( ); //聲明當(dāng)語言無法區(qū)分那是一個聲明還是一個表達式時,我們需要一個超越語言范圍的規(guī)則,而該規(guī)則會將上述式子判斷為一個“聲明“
struct和class可以相互替換,他們只是默認的權(quán)限不一樣如果一個程序員需要擁有C聲明的那種struct布局,可以抽出來單獨成為struct聲明,并且和C++部分組合起來
1.3 對象的差異
C++支持三種程序范式:程序模型、抽象數(shù)據(jù)類型模型、面向?qū)ο竽P?/strong>面向?qū)ο竽P驮诶^承體系中 ,有時候編譯期間無法確定指針或引用所指類型
C++支持的多態(tài)類型:
1. 經(jīng)由一組隱式的轉(zhuǎn)化操作:如派生類指針轉(zhuǎn)化為指向父類的指針
2. 經(jīng)由虛函數(shù)機制
3. 經(jīng)由dynamic_cast 和 typeid運算符
一個class所占的大小包括:其非靜態(tài)成員所占的大小 由于內(nèi)存對齊填補上的大小 加上支持虛函數(shù)而產(chǎn)生的大小
指針的類型,只能代表其讓編譯器如何解釋其所指向的地址內(nèi)容,和它本身類型無關(guān),所以轉(zhuǎn)換其實是一種編譯器指令,不改變所指向的地址,只影響怎么解釋它給出的地址
當(dāng)一個基類對象被初始化為一個子類對象時,派生類就會被切割用來塞入較小的基類內(nèi)存中,派生類不會留下任何東西,多態(tài)也不會再呈現(xiàn)。
Part2二、構(gòu)造函數(shù)語意學(xué)
2.1 默認構(gòu)造函數(shù)的構(gòu)造操作
以下四種情況下,會合成有用的構(gòu)造函數(shù):帶有默認構(gòu)造函數(shù)的成員函數(shù)對象,不過這個合成操作只有在構(gòu)造函數(shù)真正需要被調(diào)用時才發(fā)生,但只是調(diào)用其成員的默認構(gòu)造函數(shù),其他則不會初始化如果一個派生類的父類帶有默認構(gòu)造函數(shù),那么子類如果沒有定義構(gòu)造函數(shù),則會合成默認構(gòu)造函數(shù),如果有的話但是沒有調(diào)用父類的,則編譯器會插入一些代碼調(diào)用父類的默認構(gòu)造函數(shù)帶有一個虛函數(shù)的類類聲明(或繼承)一個虛函數(shù) 類派生自一個繼承串鏈,其中有一個或更多的虛基類帶有一個虛基類的類
C++新手常見的兩個誤解:任何class如果沒有定義默認構(gòu)造函數(shù),就會被合成出來一個編譯器合成出來的默認構(gòu)造函數(shù)會顯式設(shè)定類中的每一個數(shù)據(jù)成員的額 默認值
2.2 拷貝構(gòu)造函數(shù)的構(gòu)造操作
有三種情況會調(diào)用拷貝構(gòu)造函數(shù):對一個對象做顯式的初始化操作當(dāng)對象被當(dāng)作參數(shù)交給某個函數(shù)當(dāng)函數(shù)傳回一個類對象時
如果類沒有聲明一個拷貝函數(shù),就會有隱式的聲明和隱式的定義出現(xiàn),同默認構(gòu)造函數(shù)一樣在使用時才合成出來 什么情況下一個類不展現(xiàn)“淺拷貝語意”:當(dāng)類內(nèi)含有一個成員類而后者的類聲明中有一個拷貝構(gòu)造函數(shù)(例如內(nèi)含有string成員變量) 當(dāng)類繼承自一個基類而基類中存在拷貝構(gòu)造函數(shù)這兩個編譯器都會合成拷貝構(gòu)造函數(shù)并且安插進那個成員和基類的拷貝構(gòu)造函數(shù)當(dāng)類聲明了一個或多個虛函數(shù)編譯器會顯式的設(shè)定新類的虛函數(shù)表,而不是直接拷貝過來指向同一個當(dāng)類派生自一個繼承串鏈,其中有一個或多個虛基類編譯器會合成一個拷貝構(gòu)造函數(shù),安插一些代碼用來設(shè)定虛基類指針和偏移的初值,對每個成員執(zhí)行必要的深拷貝初始化操作,以及執(zhí)行其他的內(nèi)存相關(guān)工作
2.3 程序轉(zhuǎn)化語意學(xué)
在將一個類作為另一個類的初值情況下,語言允許編譯器有大量的自由發(fā)揮的空間,用來提升效率,但是缺點是不能安全的規(guī)劃拷貝構(gòu)造函數(shù)的副作用,必須視其執(zhí)行而定
拷貝構(gòu)造的應(yīng)用,編譯器會多多少的進行部分轉(zhuǎn)換,尤其是當(dāng)一個函數(shù)以值傳遞的方式傳回一個對象,而該對象有一個合成的構(gòu)造函數(shù),此外編譯器也會對拷貝構(gòu)造的調(diào)用進行調(diào)優(yōu),以額外的第一參數(shù)取代NRV(Named Return Value)
2.4 成員們的初始化隊伍
四種情況下你需要使用成員初始化列表當(dāng)初始化一個引用成員變量當(dāng)初始化一個const 成員變量當(dāng)調(diào)用一個基類的構(gòu)造函數(shù),而它擁有一組參數(shù)當(dāng)調(diào)用一個類成員變量的構(gòu)造函數(shù),而它擁有一組參數(shù)
class?Word{
?String?_name;
?int?_cnt;
public:
?Word(){
??_name?=?0;
??_cnt?=?0;
?}
/*使用成員列表初始化可以解決????
?????Word()?:?_name(0),_cnt(0){
?}
*/?
}
上式不會報錯,但是會有效率問題,因為這樣會先產(chǎn)生一個臨時的string對象,然后將它初始化,之后以一個賦值運算符將臨時對象指定給_name,再摧毀臨時的對象
成員初始化列表中的初始化順序是按照類中的成員變量聲明的順序,與成員初始化列表的排列順序無關(guān)
Part33、Data語意學(xué)
class?X{};
class?Y?:?public?virtual?X?{};
class?Z?:?public?virtual?X?{};
class?A?:?public?Y,public?Z?{};
sizeof(X)?//1
sizeof(Y)?//4
sizeof(Z)?//4
sizeof(A)?//8
X為1是因為編譯器的處理,在其中插入了1個char,為了讓其對象能在內(nèi)存中有自己獨立的地址Y,Z是因為虛基類表的指針A 中含有Y和Z所以是8
每一個類對象大小的影響因素:非靜態(tài)成員變量的大小virtual特性內(nèi)存對齊
3.1 數(shù)據(jù)成員的綁定
如果類的內(nèi)部有typedef,請把它放在類的起始處,因為防止先看到的是全局的和這個typedef相同的沖突,編譯器會選擇全局的,因為先看到全局的
3.2 數(shù)據(jù)成員的布局
非靜態(tài)成員變量的在內(nèi)存中的順序和其聲明順序是一致的但是不一定是連續(xù)的,因為中間可能有內(nèi)存對齊的填補物 virtual機制的指針所放的位置和編譯器有關(guān)
3.3 成員變量的存取
靜態(tài)變量都被放在一個全局區(qū),與類的大小無關(guān),正如對其取地址得到的是與類無關(guān)的數(shù)據(jù)類型,如果兩個類有相同的靜態(tài)成員變量,編譯器會暗自為其名稱編碼,使兩個名稱都不同非靜態(tài)成員變量則是直接放在對象內(nèi),經(jīng)由對象的地址和在類中的偏移地址取得,但是在繼承體系下,情況就會不一樣,因為編譯器無法確定此時的指針指的具體是父類對象還是子類對象
3.4 繼承下的數(shù)據(jù)成員
在下面給定的兩個類中依次討論不同情況:

在單一繼承沒有虛函數(shù)的情況下布局圖

這種情況下常見錯誤:可能會重復(fù)設(shè)計一些操作相同的函數(shù),我們可以把某些函數(shù)寫成inline,這樣就可以在子類中調(diào)用父類的某些函數(shù)來實現(xiàn)簡化把數(shù)據(jù)放在同一個類中和繼承起來的內(nèi)存布局可能不同,因為每個類需要內(nèi)存對齊


可見內(nèi)存大了100%
容易出現(xiàn)的不易發(fā)現(xiàn)的問題:

當(dāng)加上多態(tài)之后,對空間上增加的額外負擔(dān)包括:
導(dǎo)入一個虛函數(shù)表,表中的個數(shù)是聲明的虛函數(shù)的個數(shù)加上一個或兩個slots(用來支持運行類型識別)在每個對象中加入vptr,提供執(zhí)行期的鏈接,使每一個類能找到相應(yīng)的虛函數(shù)表加強構(gòu)造函數(shù),使它能夠為vptr設(shè)定初值,讓它指向?qū)?yīng)的虛函數(shù)表,這可能意味著在派生類和每一個基類的構(gòu)造函數(shù)中,重新設(shè)定vptr的值加強析構(gòu)函數(shù),使它能夠消抹“指向類的相關(guān)虛函數(shù)表”的vptr,vptr很可能以及在子類析構(gòu)函數(shù)中被設(shè)定為子類的虛表地址。析構(gòu)函數(shù)的調(diào)用順序是反向的,從子類到父類
以下是三種情況:不同的繼承下會有不同的布局



多重繼承
單一繼承特點:派生類和父類對象都是從相同的地址開始,區(qū)別只是派生類比較大能容納自己的非靜態(tài)成員變量
多重繼承下會比較復(fù)雜

一個派生對象,把它的地址指定給最左邊的基類,和單一繼承一樣,因為起始地址是一樣的,但是后面的需要更改,因為需要加上前面基類的大小,才能得到后面基類的地址

虛繼承
STL標準庫中使用的虛繼承:

虛繼承關(guān)系:



3.5 對象成員的效率
程序員如果關(guān)心程序效率,應(yīng)該實際測試,不要光憑推論、常識判斷或假設(shè)。優(yōu)化操作并不一定總是能夠有效運行,我不止一次以優(yōu)化方式來 編譯一個已通過編譯的正常程序,卻以失敗收場
3.6 指向數(shù)據(jù)成員的指針
vptr通常放在起始處或尾端,與編譯器有關(guān),C++標準允許放在類中的任何位置 取某個類成員變量的地址,通常取到得的是在類的首地址的偏移位置例如?& Point3d::z;?將得到在類的偏移位置,最低限度是類的成員大小總和,而這個偏移量通常都被加上了1如果用一個真正綁定類對象(也就是使用 . 操作符訪問成員變量)去取地址,得到的將會是內(nèi)存中真正的地址
在多重繼承下,若要將第二個積累的指針和一個與派生類綁定的成員結(jié)合起來,那么將會因為需要加入偏移量而變得相當(dāng)復(fù)雜
Part4四、Function 語意學(xué)
C++支持三種類型的成員函數(shù):static 、non-static 、virtualstatic函數(shù)限制:不能直接存取non-static數(shù)據(jù)不能被聲明為const
4.1 成員函數(shù)的各種調(diào)用方式
非靜態(tài)成員函數(shù):C++會保證至少和一般的普通的函數(shù)有相同的效率,經(jīng)過三個步驟的轉(zhuǎn)換改寫函數(shù),安插一個額外的參數(shù)到該函數(shù)中,用來提供一個存取管道------即this指針對每一個非靜態(tài)成員的存取操作改成使用this指針來調(diào)用將成員函數(shù)改寫成一個外部函數(shù),并且名稱改為獨一無二的
虛函數(shù)成員函數(shù):也會經(jīng)過類似的轉(zhuǎn)化例如:ptr->normalize()會被轉(zhuǎn)化為( * ptr->vptr[1] )( ptr )vptr是編譯器產(chǎn)生的指針,指向虛函數(shù)表,其名稱也會被改為獨一無二1 是該函數(shù)在虛函數(shù)表中的索引ptr 則是this指針
靜態(tài)成員函數(shù):它沒有this指針,因此會有以下限制:它不能直接存取類中的非成員變量它不能夠被聲明為const、volatile 和 virtual它不需要經(jīng)過類的對象才能被調(diào)用----雖然很多事情況是這樣調(diào)用的
4.2 詳解虛成員函數(shù)
虛函數(shù)一般實現(xiàn)模型:每一個類都有一個虛函數(shù)表,內(nèi)含該類中有作用的虛函數(shù)地址,然后每個對象有一個虛函數(shù)指針,指向虛表位置多態(tài)含義:以一個基類的指針(或引用),尋址出一個子類對象什么是積極多態(tài)?
當(dāng)被指出的對象真正使用時,多態(tài)就變成積極的了
單一繼承虛函數(shù)布局圖

一個類派生自Point會發(fā)生什么事?三種可能的情況它可以繼承基類所聲明的虛函數(shù)的函數(shù)實例,該函數(shù)實例的地址會被拷貝進子類的虛表的相對應(yīng)的slot之中它可以使用自己的虛函數(shù)實例----它自己的函數(shù)實例地址必須放在對應(yīng)的slot之中它可以加入一個新的虛函數(shù),這時候虛函數(shù)表的尺寸會增加一個slot,而新加入的函數(shù)實例地址也會被放進該slot之中
多重繼承下的虛函數(shù)

虛擬繼承下的虛函數(shù)

4.4指向成員函數(shù)的指針
對于普通的成員函數(shù),編譯器會將其轉(zhuǎn)化為一個函數(shù)指針,然后使用成員函數(shù)的地址去初始化
例如 ?double (Point :: *pmf ) ( );轉(zhuǎn)化為??double ( Point :: coord )( ) = &Point :: x;這樣調(diào)用??(coord) (& origin)或 (coord)(ptr)
對一個虛函數(shù)取地址,在vc編譯器下,要么得到vacll thunk地址(虛函數(shù)時候),要么得到的是函數(shù)地址(普通函數(shù))
4.5 內(nèi)聯(lián)函數(shù)
inline只是向編譯器提出一個請求,是否真的優(yōu)化取決于編譯器自己的判定對于形式參數(shù),會采用:常量表達式替換 常量替換 引入臨時變量來避免多次求值操作
對于局部變量,會采用:使用臨時變量
Part5五、構(gòu)造、析構(gòu)、拷貝語意學(xué)
應(yīng)注意的一些問題:構(gòu)造函數(shù)不要寫為純虛函數(shù),因為當(dāng)抽象類中有數(shù)據(jù)的時候,將無法初始化把所有函數(shù)設(shè)計成虛函數(shù),再由編譯器去除虛函數(shù)是錯誤的,不應(yīng)該成為虛函數(shù)的函數(shù)不要設(shè)計成虛函數(shù)當(dāng)你無法抉擇一個函數(shù)是否需要為const時,尤其是抽象類,最好不設(shè)置成const
5.1 "無繼承" 情況下的對象構(gòu)造
對于沒有初始化的全局變量,C語言中會將其放在一個未初始化全局區(qū),而C++會將所有全局對象初始化對于不需要構(gòu)造函數(shù)、析構(gòu)函數(shù)、賦值操作的類或結(jié)構(gòu)體,C++會將其打上POD標簽,賦值時按照c那樣的位搬運對于含有虛函數(shù)的類,編譯器會在構(gòu)造函數(shù)的開始放入一些初始化虛表和虛指針的操作面對函數(shù)以值方式返回,編譯器會將其優(yōu)化為加入一個參數(shù)的引用方式,避免多次構(gòu)造函數(shù)
5.2 繼承體系下的對象構(gòu)造
構(gòu)造函數(shù)會含有大量的隱藏嘛,因為編譯器會進行擴充:記錄在成員初始化列表中的成員數(shù)據(jù)初始化會被放進構(gòu)造函數(shù)的本體,并以成員在類中聲明的順序為順序 如果有一個成員并沒有出現(xiàn)在成員初始化列表中,但是它由一個默認構(gòu)造函數(shù),那么也會被調(diào)用在那之前,如果類對象由虛表指針,它必須被設(shè)定初值,指向適當(dāng)?shù)奶摫?/strong>在那之前,所有上一層的基類構(gòu)造函數(shù)必須被調(diào)用,以基類聲明的順序(不是成員初始化列表出現(xiàn)的順序)如果基類被列于成員初始化列表中,那么任何顯式指定的參數(shù)應(yīng)該傳遞過去如果基類沒有被列于基類初始化列表中,而它有默認的構(gòu)造函數(shù),那么就調(diào)用如果基類是多重繼承下的第二個或后繼的基類,那么this指針必須有所調(diào)整在那之前,所有虛基類構(gòu)造函數(shù)必須被調(diào)用,從左到右,從最深到最淺如果類被列于成員初始化列表中,那么如果有任何顯式指定的參數(shù),都應(yīng)該傳遞過去。若沒有列于list中,而類中有一個默認構(gòu)造,亦應(yīng)該調(diào)用此外,類中的每一個虛基類的偏移位置必須在執(zhí)行期可被存取如果類對象是最底層的類,其構(gòu)造函數(shù)可能被調(diào)用,某些用以支持這一行為的機制必須被放進來
不要忘記在賦值函數(shù)中,檢查自我賦值的情況
5.3 對象復(fù)制語意學(xué)
當(dāng)一個類復(fù)制給另一個類時,能采用的有三種方式:什么都不做,會實施默認行為如果有需要,會自動生成一個淺拷貝,至于什么時候需要深拷貝(見第二章講)提供一個拷貝復(fù)制運算符顯式地拒絕把一個類拷貝給另一個
虛基類會使其復(fù)制操作調(diào)用一次以上,因此我們應(yīng)該避免在虛基類中聲明數(shù)據(jù)成員
5.5 析構(gòu)語意學(xué)
什么時候會合成析構(gòu)函數(shù)?在類內(nèi)含的成員函數(shù)有析構(gòu)函數(shù)基類含有析構(gòu)函數(shù)
析構(gòu)的正確順序:析構(gòu)函數(shù)的本體首先被執(zhí)行,vptr會在程序員的代碼執(zhí)行前被重設(shè)。如果類擁有成員類對象,而后者擁有析構(gòu)函數(shù),那么他們會以其聲明順序的相反順序被調(diào)用如果類內(nèi)涵一個vptr,現(xiàn)在被重新設(shè)定,指向適當(dāng)?shù)姆e累的虛表如果有任何直接的非虛基類擁有析構(gòu)函數(shù),它們會以其聲明順序的相反順序被調(diào)用如果有任何虛基類擁有析構(gòu)函數(shù),而且目前討論的這個類是最尾端的類,那么它們會以其原來的構(gòu)造順序的相反順序被調(diào)用
Part6六、執(zhí)行期語意學(xué)
C++難以從程序源碼看出表達式的復(fù)雜過程,因為你并不知道編譯器會在其中添加多少代碼
編譯器對不同的對象會做不同的操作:
對于全局對象:編譯器會在添加__main函數(shù)和_exit函數(shù)(和C庫中的不同),并且在這兩個函數(shù)中對所有全局對象進行靜態(tài)初始化和析構(gòu)

使用被靜態(tài)初始化的對象,有一些缺點:
如果異常處理被支持,那么那些對象將不能被放置到try區(qū)段之內(nèi)增加了程序的復(fù)雜度
因此,不建議用那些需要靜態(tài)初始化的全局對象
對于局部靜態(tài)對象:
會增加臨時的對象用來判斷其是否被構(gòu)造,用來保證在第一次進入含有該靜態(tài)對象的起始處調(diào)用一次構(gòu)造函數(shù),并且在離開文件的時候利用臨時對象判斷是否已經(jīng)被構(gòu)造來決定是否析構(gòu)
對于對象數(shù)組:(例如Point knots[10])
如果對象沒有定義構(gòu)造函數(shù)和析構(gòu)函數(shù),那么編譯器只需要分配需要存儲10個連續(xù)空間即可 如果有構(gòu)造函數(shù),且如果有名字,則會分為是否含有虛基類調(diào)用不同的函數(shù)來構(gòu)造,如果沒有名字,例如沒有knots,則會使用new來分配在堆中 當(dāng)聲明結(jié)束時會有類似構(gòu)造的析構(gòu)過程 我們無法在程序中取出一個構(gòu)造函數(shù)的地址
6.2 new 和 delete 運算符
對于普通類型變量:例如int *pi = new int(5) 調(diào)用函數(shù)庫中的new運算符if(int *pi = _new (sizeof(int) )) 再配置初值 *pi = 5 對于delete來說 delete pi; 則先進行保護 if( pi != 0) 再調(diào)用delete ?、_delete(pi)
對于成員對象:
對于Point3d* origin = new Point3d
實際調(diào)用operator new,其代碼如下
????extern?void*?operator?new?(size_t?size){
????????if(size?==?0)
????????????size?=?1;
????????void?*?last_alloc;
????????while(!(last_alloc?=?malloc(size))){
????????????if(_new_handler)
????????????????(?*_new_handler)();
????????????else
????????????????return?0;
????????}
????????return?last_alloc;
????}
語言要求每一次對new的調(diào)用都必須傳回一個獨一無二的指針,為了解決這個問題,傳回一個指向默認為1Byte的內(nèi)存區(qū)塊,允許程序員自己定義_new_handler函數(shù),并且循環(huán)調(diào)用
至于delete也相同
????extern?void?operator?delete?(void?*ptr){
?????if(ptr)
??????free(?(char*)ptr)
????}
對于對象數(shù)組,會在分配的內(nèi)存上方放上cookies,來存儲數(shù)組個數(shù),方便delete調(diào)用來析構(gòu)
程序員最好避免以一個基類指向一個子類所組成的數(shù)組---如果子類對象比其基類大的話
解決方式:
?for(int?ix?=?0;?ix???Point3d?*p?=?&((Point3d*)ptr)[ix];
??delete?p;
?}
程序員必須迭代走過整個數(shù)組,把delete運算符實施與每一個元素身上。以此方式,調(diào)用操作將是virtual。因此,Point3d和Point的析構(gòu)函數(shù)都會實施于每一個對象上
Placement Operator new的語意
有一個預(yù)先定義好的重載的new運算符,稱為placement operator new。它需要第二個參數(shù),類型為void*
形如?Point2w *ptw = new (arena) Point2w,其中arena指向內(nèi)存中的一個區(qū)塊,用以放置新產(chǎn)生出來的Point2w 對象
?void*?operator?new(size_t?,?void*?p){
??return?p;
?}
如果我們在已有對象的基礎(chǔ)上調(diào)用placement new的話,原來的析構(gòu)函數(shù)并不會被調(diào)用,而是直接刪除原來的指針,但是不能使用delete 原來的指針
正確的方法應(yīng)該是 :
????//錯誤:
????delete?p2w;
????p2w?=?new(arwna)?Point2w;
????//正確:
????p2w->Point2w;
????p2w?=?new(arena)?Point2w;
6.3 臨時性對象
臨時對象在類的表達式并賦值,函數(shù)以值方式傳參等都會產(chǎn)生臨時對象-----而臨時對象會構(gòu)造和析構(gòu),所以會拖慢程序的效率,我們應(yīng)該盡量避免
Part7七、站在對象模型的頂端
三個著名的C++語言擴充性質(zhì):模板、異常(EH)、RTTI(runtime type identification)
7.1 Template
模板實例化時間線:
當(dāng)編譯器看到模板類的聲明時,什么都不會做,不會進行實例化模板類中明確類型的參數(shù),通過模板類的某個實例化版本才能存取操作即使是靜態(tài)類型的變量,也需要與具體的實例版本關(guān)聯(lián),不同版本有不同的一份如果聲明一個模板類的指針,那么不會進行實例化,但是如果是引用,那么會進行實例化對于模板類中的成員函數(shù),只有在函數(shù)使用的時候才會進行實例化
模板名稱決議的方法-----即如果非成員函數(shù)在類中調(diào)用,那么會調(diào)用名稱相同的哪個版本:
會根據(jù)該函數(shù)是否與模板有關(guān)來判斷,如果是已知類型,那么會在定義的范圍內(nèi)直接查找,如果依賴模板的具體類型,那么會在實例化的范圍查找
7.2 異常處理
C++異常處理由三個主要的語匯組件:
一個throw子語。它在程序某處發(fā)出一個exception,exception可以說內(nèi)建類型也可以是自定義類型一個或多個catch子句。每一個catch子句都是一個exceotion hander,它用來表示說,這個子句準備處理某種類型exception,并且在封閉的大括號區(qū)段中提供實際的處理程序一個try區(qū)段。它被圍繞一系列的敘述句,這些敘述句可能會引發(fā)catch子句起作用
當(dāng)一個異常被拋出去,控制權(quán)會從函數(shù)調(diào)用中被釋放出來,并尋找一個吻合的catch子句。如果都沒有吻合者,那么默認的處理例程 terminate()會被調(diào)用,當(dāng)控制權(quán)被放棄后,堆棧中的每一個函數(shù)調(diào)用也就被推離。這個程序被稱為 unwingding the stack 。在每一個函數(shù)被推離堆棧之前,函數(shù)的局部類的析構(gòu)會被調(diào)用
因此一個解決辦法就是將類封裝在一個類中,這樣變成局部類,如果拋出異常也會被自動析構(gòu)當(dāng)一個異常拋出,編譯系統(tǒng)必須:
檢查發(fā)生throw操作的函數(shù) 決定throw操作是否發(fā)生在try區(qū)段 若是,編譯系統(tǒng)必須把異常類型拿來和每一個catch子句進行比較 如果比較后吻合,流程控制應(yīng)該交到catch子句中 如果throw的發(fā)生并不在try區(qū)段中,或沒有一個catch子句吻合,那么系統(tǒng)必須:摧毀所有活躍局部類 從堆棧中將目前的函數(shù)(unwind)釋放掉 進行到程序堆棧的下一個函數(shù)中去,然后重復(fù)上述步驟2—5
7.3 執(zhí)行期類型識別
當(dāng)兩個類有繼承關(guān)系的時候,我們有轉(zhuǎn)換需求時,可以進行向下轉(zhuǎn)型,但是很多時候是不安全的C++的RTTI (執(zhí)行期類型識別)提供了一個安全的向下轉(zhuǎn)型設(shè)備,但是只對多態(tài)(繼承和動態(tài)綁定)的類型有效,其用來支持RTTI的策略就是,在C++的虛函數(shù)表的第一個slot處,放一個指針,指向保存該類的一些信息----即type_info類(在編譯器文件中可以找到其定義)dynamic_cast運算符可以在執(zhí)行期決定真正的類型對于指針來說:如果轉(zhuǎn)型成功,則會返回一個轉(zhuǎn)換后的指針如果是不安全的,會傳回0放在程序中通過 if 來判斷是否成功,采取不同措施對于引用來說:如果引用真正轉(zhuǎn)換到適當(dāng)?shù)淖宇悾瑒t可以繼續(xù)如果不能轉(zhuǎn)換的話,會拋出 bad_cast 異常通常使用 try 和 catch 來進行判斷是否成功
Typeid運算符:可以傳入一個引用,typeid運算符會傳回一個const reference,類型為type_info。其內(nèi)部已經(jīng)重載了 == 運算符,可以直接判斷兩個是否相等,回傳一個bool值例如 ?if( typeid( rt ) == typeid( fct ) )RTTI雖然只適用于多態(tài)類,但是事實上type_info object也適用于內(nèi)建類,以及非多態(tài)的使用者自定類型,只不過內(nèi)建類型 得到的type_info類是靜態(tài)取得,而不是執(zhí)行期取得。

品味經(jīng)典

▊《C++ Primer中文版(第5版)》
Stanley B. Lippman,Josee Lajoie,Barbara E. Moo 著
王剛 楊巨峰 譯?
如果只讀一本C++書,本書將是你永不局悔的選擇
征服全球數(shù)千萬讀者的大師之作
C++學(xué)習(xí)頭牌,技術(shù)影響力圖書冠軍
真正暢行全球20年的C++入門必讀經(jīng)典,惠及數(shù)百萬高校師生啟蒙5代國產(chǎn)程序員,語言締造者與常青藤名校數(shù)版迭代的杰作,系統(tǒng)透徹:從初學(xué)到專家可全程案頭備用。
(快快掃碼搶購吧!)

▊《Effective C++:改善程序與設(shè)計的55個具體做法(第三版)中文版》
Scott Meyers 著
侯捷 譯
一本輕薄短小高密度的“專家經(jīng)驗累積”
國際影響力波及了整個計算機技術(shù)出版領(lǐng)域
本書不是讀完一遍就可以束之高閣的快餐讀物,也不是用以解決手邊問題的參考手冊,而是需要您去反復(fù)閱讀體會的,C++是真正程序員的語言,背后后精神的思想與無以倫比的表達能力,這使得它具有類似宗教般的魅力。希望這本書能夠幫您跨越C++的重重險阻,領(lǐng)略高處才有的壯美風(fēng)光,做一個成功而快樂的C++程序員。

Scott Meyers 著
侯捷 譯
梅耶爾大師Effective三部曲之一
繼Effective C++之后,Scott Meyers于1996推出這本“續(xù)集”。條款變得比較少,頁數(shù)倒是多了一些,原因是這次選材比“第一集”更高階。
(京東滿100減50,快快掃碼搶購吧!)

▊《深度探索C++對象模型》
Stanley,B. Lippman?著
侯捷?譯
一位偉大的C++編譯程序設(shè)計者向你闡述他如何處理各種explicit(明確出現(xiàn)于C++程序代碼中)和implicit(隱藏于程序代碼背后)的C++語意
本書專注于C++面向?qū)ο蟪绦蛟O(shè)計的底層機制,包括結(jié)構(gòu)式語意、臨時性對象的生成、封裝、繼承,以及虛擬――虛擬函數(shù)和虛擬繼承。
這本書讓你知道:一旦你能夠了解底層實現(xiàn)模型,你的程序代碼將獲得多么大的效率。Lippman澄清了那些關(guān)于C++額外負荷與復(fù)雜度的各種錯誤信息和迷思,但也指出其中某些成本和利益交換確實存在。他闡述了各式各樣的實現(xiàn)模型,指出它們的進化之道及其本質(zhì)因素。書中涵蓋了C++對象模型的語意暗示,并指出這個模型是如何影響你的程序的。
(京東滿100減50,快快掃碼搶購吧!)

【德】Nicolai M. Josuttis 著
侯捷 譯
全球C++經(jīng)典權(quán)威參考書
1100頁鴻篇巨著,基于C++11重寫全書示例代碼
標準庫提供了一組公共類和接口,極大地拓展了C++語言核心功能。本書詳細講解了每一標準庫組件,包括其設(shè)計目的和方法、復(fù)雜概念的剖析、實用而高效的編程細節(jié)、存在的陷阱、重要的類和函數(shù),又輔以大量用C++11標準實現(xiàn)的實用代碼范例。除覆蓋全新組件、特性外,本書一如前版,重點著眼于標準模板庫(STL),涉及容器、迭代器、函數(shù)對象以及STL算法。此外,本書同樣關(guān)注lambda表達式、基于區(qū)間的for循環(huán)、move語義及可變參數(shù)模板等標準庫中的新式C++編程風(fēng)格及其影響。
(京東滿100減50,快快掃碼搶購吧!)

▊《C++服務(wù)器開發(fā)精髓》
張遠龍?著
從操作系統(tǒng)原理角度講解C++服務(wù)器開發(fā)技術(shù)棧
內(nèi)容詳盡細致、版本新
重磅級C++服務(wù)器開發(fā)紅寶書
本書詳細講解如何掌握C++服務(wù)器開發(fā)技術(shù),以及如何成為合格的C++開發(fā)者,秉承的思想是,通過掌握技術(shù)原理,可以輕松制造“輪子”,靈活設(shè)計出優(yōu)雅、魯棒的服務(wù),并快速學(xué)習(xí)新技術(shù)。
無論是對于C/C++開發(fā)者、計算機專業(yè)的學(xué)生,還是對于想了解操作系統(tǒng)原理的讀者,本書都極具參考價值。
(掃碼了解本書詳情)
如果喜歡本文 歡迎?在看丨留言丨分享至朋友圈?三連 ?熱文推薦??
