1. 程式人生 > >《深度探索c++物件模型》學習筆記

《深度探索c++物件模型》學習筆記

1、c++的佈局和存取時間成本?

封裝並未給c++帶來任何的空間或執行期的不良後果,c++在佈局和存取時間上的主要額外負擔由虛擬化引起。包括:
1)virtual function機制。用以支援一個有效率的“執行期繫結”。
2)virtual base class機制。用以實現“多次出現在繼承體系中的base class,有一個的單一而被共享的例項”。

2、虛擬繼承

虛擬繼承是多重繼承中特有的概念。虛擬基類是為解決多重繼承而出現的。
在虛擬繼承的情況下,基類不管在繼承串鏈中被派生多少次,永遠只會存在一個例項。優勢主要體現在菱形繼承當中。虛擬繼承與普通繼承不同的是,虛擬繼承可以防止出現diamond繼承時,一個派生類中同時出現了兩個基類的子物件

。也就是說,為了保證這一點,在虛擬繼承情況下,基類子物件的佈局是不同於普通繼承的。因此,它需要多出一個指向基類子物件的指標

class iostream:public istream,public ostream{...};

class istream:virtual public ios {...};
class ostream:virtual public ios {...};

這裡寫圖片描述
如圖所示菱形繼承,在iostream之中就只有虛基類ios的一個例項!!!

1)時間:繼承類物件訪問虛基類物件中的成員時通過間接引用完成,增加引用定址時間。
2)空間:由於共享所以不必要在物件記憶體中儲存多份虛基類子物件的拷貝,較之多繼承節省空間。

3、c++支援多型的方法

1)經由一組隱式的轉化操作。例如把一個派生類指標轉化為一個指向其基類的指標(也就是基類指標操作子類物件):

shape *ps = new circle();

2)經由虛擬函式機制:

ps->rotate();

3)經由dynamic_cast和typeid運算子:

if(circle *pc = dynamic_cast<circle*>(ps))...

在c++中,多型表示:以一個基類指標或引用,定址出一個派生類物件。多型的主要用途是經由一個共享的介面來影響型別的封裝,共享介面以虛擬函式機制引發,在執行期根據物件的真正型別解析出到底是哪一個函式例項被呼叫。
識別一個類是否支援多型,就是看它是否有虛擬函式。Vtbl中的虛擬函式一定在編譯期間獲知,其函式的個數、位置和地址是固定不變的,執行期間不能增、改、刪。
執行期三步完成虛擬函式呼叫:1)由vptr找到vtbl;2)定位vtbl中的slot(索引值);3)通過該索引下的值調函式。

4、C++ 編譯器生成預設建構函式的四種情況

c++新手的兩個誤解:
1)任何類如果沒有定義預設建構函式,就會被合成出來一個。
2)編譯器合成出來的預設建構函式會顯式設定“類內每一個數據成員的預設值”。
上述兩種說法都是錯誤的!

C++ 編譯器生成預設建構函式的四種情況:
1)類成員中有成員是類物件,並且該成員的類含有預設建構函式。那麼C++編譯器會給這個類也生成一個預設建構函式,用來呼叫其成員物件的建構函式,完成該成員的初始化構造。如果這個成員的類沒有給出預設建構函式,那麼C++編譯器也不會生成該類的預設建構函式。
2)這個類的基類有預設建構函式。那麼C++編譯器也會幫你生成該派生類的預設建構函式,以呼叫基類的預設建構函式,完成基類的初始化。如果基類沒有提供這個預設構造的函式,那麼編譯器也不會為派生類生成預設的建構函式(這裡包括兩層意思,第一,基類沒有任何形式建構函式;第二,基類存在其他形式的非預設建構函式,這種型別就是編譯不過的,道理很明顯)。
3)類中存在虛擬函式(新定義或繼承而得到)。那麼C++編譯器會為你生成預設建構函式,在編譯期生成虛表和虛表指標。
4)存在虛基類(有直接虛擬基類或繼承鏈上有虛基類)。那麼C++編譯器會為你生成預設建構函式,以初始化虛基類表(vbtable)。

這四種情況之外,且沒有宣告任何constructor的類,可以說它有無用的建構函式,但實際上它根本就不會被構建出來。

5、位逐次拷貝(bitwise copy semantics)

Default constructors 和 copy constructors 在必要的時候才由編譯器產生出來。“必要”即意指當class不展現bitwise copy semantics時。

也就是,如果class中展現了位逐次拷貝,編譯器就不會產生出拷貝建構函式。當沒有產生拷貝構造的時候,我們的類怎麼產生呢。就是通過 bitwise copy 來搞定,也就是 將源類中成員變數中的每一位都逐次拷貝到目標類中,這樣我們的類就構造出來了。

類在下述四種情況下不展現位逐次拷貝(這時才會而且有必要生成拷貝構造):
1)類中含有成員類物件,並且此類物件含有預設建構函式。即:有“物件”成員,而非只有基本資料型別成員。
2)基類帶有拷貝建構函式。
3)類中存在虛擬函式(新定義或繼承而得到)。重新設定vptr,並且,如果該物件是第一個物件的話還會構造vtbl。
4)存在虛基類(有直接虛擬基類或繼承鏈上有虛基類)
前兩種情況下,都是需要生成該類的拷貝構造去呼叫類成員物件或基類的拷貝建構函式。
後兩種情況下,需要編譯器來完成虛擬函式表(vbtl)的初始化和虛表指標(vptr)的初始化,所以如果沒有顯式的定義建構函式,需要編譯器構造預設的建構函式。

6、必須使用成員初始化列表的情況

1)初始化引用型別成員;
2)初始化const成員;
3)基類建構函式有引數;
4)成員類物件的建構函式有引數。

初始化順序由宣告順序決定,而與初始化列表中的順序無關,因此編譯器會對初始化列表一一處理並有可能重新排序。

7、 繼承對類的資料成員的影響

1、 簡單無多型的繼承。不會增加記憶體空間以及存取上的額外負擔。

2、加上多型(有虛擬函式)後,會有額外時間空間開銷:
1)生成一個和類有關的虛表,用來存放宣告的每一個虛擬函式地址。表中元素個數一般而言是被宣告的虛擬函式個數,再加上一個或兩個slots用以支援執行時型別識別(RTTI)。
2)在每一個類物件匯入一個虛表指標,提供執行期連結,是每一個物件找到相應的虛表。
3)加強建構函式,使它能夠為虛表指標設定初值,指向類的虛表。
4)加強解構函式,以處理vptr。

8、多重繼承的資料佈局

這裡寫圖片描述
這裡寫圖片描述
繼承關係如下:
這裡寫圖片描述

那麼相應的資料佈局為:
這裡寫圖片描述

9、Static成員函式特性:

1)沒有this指標
2)不能直接存取class中的非靜態成員;
3)不能被宣告為const、volatile或virtual;
4)可以不用物件訪問。
Static成員函式,由於沒有this,所以可以作為callback函式的候選,或者作為執行緒的主函式。

10、繼承體系下帶有資料成員的類的構造過程

建構函式的呼叫順序:從root到leaf,從left到right
1)虛基類的建構函式,從左到右,從最頂層到最底層。它和非虛基類不同:是由最底層子類呼叫的。
2)非虛基類的建構函式,按照基類被宣告順序從左到右呼叫。它與虛基類的不同:是由直接子類呼叫的。
3)如果類中有虛表指標,則設定vptr初值;若增加有新的虛擬函式或者覆蓋基類虛擬函式,則修改vtbl內的資訊。
4)成員變數以其宣告順序進行初始化構造。
5)建構函式中,使用者自定義的程式碼(user code)最後被執行。