c++對象模型解析(一)

    本系列記錄在學習過程中,我對於c++對象模型的理解,主要包括如下知識點等底層實現機制。

目錄

struct 和 class之間關係區別

多態的實現方法

對象的大小

構造函數

NRV(Named Return Value)優化

inline函數、構造函數與虛函數

數據繼承模型


struct 和 class之間關係區別

  1. 成員變量權限,首先struct中的成員變量默認是私有的,class的成員變量默認是共有的
  2. 繼承方式,struct默認是私有繼承,class默認是公有繼承
  3. 在定義模板參數的時候只能用class,不能用struct
  4. 最重要的一點是語義上,關鍵詞本身並不提供差異,當看到一個struct,下意識會想到struct其實是一個“數據集合”的意思,而class則是一個“類”的意思,幷包含了封裝、繼承、多態等屬性

多態的實現方法

    多態指的是同一個語句的不同表現形態(同一個接口,不同的實現形態)。

  1. 經由一組隱式轉換操作,比如將一個指向derived類對象的指針轉換成指向父類的指針
  2. 經由virtual function機制
  3. 經由dynamic_cast 和 typ_id運算符

    這裏要理解一個問題,爲什麼只能通過引用或者指針的形式才能觸發多態,而一個普通的賦值不能觸發多態機制?如果通過普通的賦值則會發生切割對象的問題,即把部分子類對象的成員變量切割掉,而指針則不會,因爲其大小不變,根據指針類型變化的只有指針所指內存大小,這纔會產生多態。引用本質實現上也是指針,所以相一致。

對象的大小

    一般而言有三種組成,

  1. non-static 數據成員變量大小總和
  2. 如果類中定義了虛函數,則需要加虛指針的大小
  3. 對其所填補的空間,如32位機器調整到4字節的整數倍,目的是內存與cpu之間總線傳輸每次能傳完整數倍,提高效率

構造函數

  • 什麼時候編譯器會爲類A產生默認構造函數?

    首先編譯器爲類A產生默認構造函數肯定是因爲覺得構造函數有必要(nontrivial)且這個類的創建者沒有寫構造函數,但是有一點要注意,這個產生的默認構造函數只是滿足編譯器需求,並不一定滿足程序的要求,所以如果要指定對成員變量進行初始化,還是最好自己生成構造函數。

  1. 類A成員中有一個類B的對象,類B是有構造函數的(組合關係)
  2. 類A繼承於另外一個類B,B有構造函數
  3. 類A聲明瞭虛函數或者有繼承的虛函數
  4. 類繼承於虛基類
  • 什麼時候編譯器會爲類產生拷貝構造函數?

    以下四種情況,若無顯示定義拷貝構造函數,則編譯器會自動生成默認拷貝構造函數(和默認構造函數是一樣的),因爲編譯器必須將成員或者基類的拷貝構造函數在合成的拷貝構造函數中調用。

  1. 當類A內含有一個類B的成員變量,而且其擁有拷貝構造函數
  2. 當類A繼承自一個基類B,基類B有拷貝構造函數
  3. 當類A聲明一個或多個虛函數
  4. 當類A繼承自一個或多個虛基類
  • 什麼時候必須使用成員初始化列表?

    成員初始化列表是構造函數用於按順序初始化成員變量,其順序最好和變量的聲明順序一致,不一致會導致一些問題(且較難debug,別問爲啥知道),除此之外還有一點,編譯器保證初始化列表中的初始化操作會比函數主體中的初始化要前,且代價較小,以下四種情況必須使用初始化列表:

  1. 當初始化一個變量引用時
  2. 當初始化一個const變量時
  3. 當調用一個基類的構造函數,且其擁有參數
  4. 當調用一個成員變量的構造函數,且其擁有參數

NRV(Named Return Value)優化

    這個是編譯器對於c++中函數中返回局部變量的一種優化方法,即把其通過傳入一個引用參數而取出。注意一點,《對象模型》說道“只有類中定義了顯示拷貝構造函數,才能觸發編譯器NRV”,但實際上如今的g++無論你是否定義拷貝構造,都會進行優化。

具體實現步驟:

  1. 函數添加一個額外參數,爲返回對象的引用;
  2. 函數調用前,先申請欲返回對象x2的內存空間;
  3. 將對象x2的引用傳入函數中,並在函數返回前,調用x2的拷貝構造函數。
class X{...};

X foo(){
    X x1;
    //對x1進行處理
    return x1;
}

X x2=foo();

--------優化後------

void foo(X& x2){

    X x1;
    //對x1進行處理
    x2.X::X(x1);//調用拷貝構造函數
    return;
}

X x2;
foo(x2);

inline函數、構造函數與虛函數

  • inline函數能否定義爲虛函數?

首先,inline函數的意思就是減少函數調用的開銷,當遇到函數調用的時候,直接將函數主體遷移至調用的地方,而且是在編譯期間做的,是靜態行爲,而虛函數則需要在運行期間才能確定動態類型,兩者產生衝突,除此之外,我們定義的inline函數也只是對編譯器的一種推薦,具體編譯器是否聲明其爲inline函數有自己的規則,所以最好不要將inline函數定義爲虛函數。

  • 構造函數能否定義爲虛函數?構造函數中能否調用虛函數?

首先先明確結論,構造函數不能定義爲虛函數,析構函數可以定義爲虛函數(這個問題不解釋了),而且不要在構造函數和析構函數中調用虛函數(Effective C++條款9,主要是基類和派生類的構造順序有關)。虛表存放在進程空間中的數據段中,由多個對象所共享,所以可以看成靜態成員變量,而虛指針則是在類對象的內部,在構造函數(進入構造函數體之前,也就是和初始化列表一樣)初始化

  1. 虛函數是需要一個虛表存放其地址,然後每個對象通過虛指針指向這個虛表進而訪問虛函數,而對象恰恰是通過構造函數來初始化虛指針和虛表的,所以兩者矛盾。
  2. 虛函數的作用在於通過父類的指針或者引用來調用它的時候能夠變成調用子類的相應的成員函數。而構造函數是在創建對象時自動調用的,不可能通過父類的指針或者引用去調用,因此也就規定構造函數不能是虛函數。

數據繼承模型

  • 當一個對象沒有加入多態機制(即對象內部沒有聲明虛函數),對象的大小隻用關心數據大小和alignment即可。這裏還需要注意點,當派生類繼承基類的時候,基類的大小已經添加過alignment,派生類對象的大小基於此再增加。
  • 單一繼承當加上多態機制後,就要付出額外的代價:
  1. 新建一個與類相關的虛表,用來存放虛指針的地址,虛表中的元數個數一般爲類中聲明的虛函數個數+一個slot(RTTI)
  2. 在每個對象中都有一個虛指針指向虛表,用於運行期使得對象能夠找到虛表
  3. 構造函數能夠初始化虛指針,讓其指向類所對應的虛表
  4. 析構函數能夠析構虛指針

  • 多重繼承時候,情況會變得不一樣,圖中可以看到Vertex3d類對象中包含兩個虛指針分別指向兩個虛表,若在Vertex3d類中重寫了虛函數,則會將其替換到對應虛表中的地址,若Vertex3d類中新聲明瞭虛函數,根據繼承順序, 新聲明虛函數的地址會附加到第一個虛函數表指針指向的虛函數表的後面。

  • 虛擬繼承,虛擬繼承解決多重繼承中的菱形繼承問題。數據佈局模型如下圖所示,首先,Point3d虛繼承Point2d,有一個虛指針指向自己的虛表(這個和單一繼承模型分開,可以看最後一個Vertex3d對象就沒有自己的虛指針,說明這個只有虛繼承情況下纔會有),還有一個虛基類指針指向基類(共享數據)

如下一道虛繼承的面試題。32位機器上,比較四種情況的對象大小

                                                                                                                                                                                                             

參考:

博客 https://www.cnblogs.com/yinheyi/p/10525543.html

博客 https://www.cnblogs.com/BeyondAnyTime/archive/2012/06/05/2537451.html

書籍 深入理解c++對象模型                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章