天天看點

C++對象模型的探索

類的對象空間

一個空類的對象占1個位元組,因為對象需要記憶體,有記憶體就需要位址

隻有一個char類,也是占一個位元組,隻有一個int32_t類,占4個位元組

類的成員函數,不論是非靜态還是靜态都不占用類對象空間

一個類有一個虛函數,就會有一個指向這個虛函數的指針(vtbl),有兩個虛函數,就會有兩個指向虛函數的指針;用來存放這些指向虛函數的指針的表稱為虛函數表(virtual table);同時這個虛函數表會儲存在可執行檔案中,在程式執行的時候載入到記憶體中

一個類中如果有虛函數,不論幾個虛函數,類的對象都隻會多4個位元組,因為有虛函數,這個對象添加一個指針(vptr),指向類的虛函數表

記憶體對齊,是編譯器對成員之間的記憶體占用做了調整,主要為了提高通路速度

this指針

子類對象是包含父類對象的,如果子類隻有一個父類,那麼這個子類對象的位址(this)和父類對象的位址(this)相同

如果子類繼承多個父類,那麼子類的對象位址和第一個父類對象位址相同

C++對象模型的探索

c 類繼承a,b 類,是以c類對象的位址和a類對象的位址相同

調用哪個父類的成員函數,這個this指針就會被編譯器自動調整到對象記憶體布局中,對應這個父類對象的起始位址

構造函數

如果我們沒有定義預設構造或者拷貝構造或者指派構造,編譯器會在某些情況下,給我們自動生成對應的構造函數

預設構造函數

預設構造函數:沒有參數的構造函數

一個類中定義的類 類型的對象的成員函數,且這個類是有構造函數的,才會被編譯器預設生成構造函數,目的在于 要調用這個類 類型對象的構造函數

一個類有父類,并且這個父類有構造函數,才會被編譯器預設生成構造函數,目的在于要調用父類的構造函數

一個類中有虛函數,會被編譯器預設生成構造函數,目的在于把類的的虛函數的位址 給類對象的虛函數表指針

有虛基類,也會被編譯器預設生成構造函數

虛基類 :一般是三層,爺爺Grand, 有兩個爹 A,A2 ,有孫子C,

虛基類會有一個虛基類表,c對象中隻會含有一份Grand對象空間

C++對象模型的探索

類的預設構造函數做了哪些事情:

1、如果有虛函數,生成虛函數表,并把類的虛函數表位址賦給對象的虛函數表指針

2、調用父類的構造函數

3、初始化

拷貝構造函數

拷貝構造函數是構造一個新的對象,指派構造函數是指派一個已經存在的對象

class A
{
  public:
    int m_test;
};
class B
{
  public:
  
  A TestFunc(){
    A test_a;
    test_a.m_test = 19;
    return test_a;// 先 生成一個臨時A對象,然後調用拷貝構造函數,構造這個臨時A 對象,然後析構test_a 對象,後再将這個臨時對象傳回
  }
}

int main()
{
  A test_A;
  test_A.m_test = 17;
  A mya2 = mya1; // 調用拷貝構造函數
}      

編譯器生成拷貝構造函數的條件與上述生成預設構造函數的條件類似

虛函數

虛函數表的指針在對象記憶體的起始位置還是末端位置,取決于編譯器,一般是在開頭的位置

一個類有虛函數就會有一個虛函數表,表中存放的是指向虛函數的指針,當執行個體化一個對象時,這個對象會産生一個指向這個虛函數表的指針; 當有多個同屬于一個類的對象時,共享相同的虛函數表(vptr),指向虛函數表的位址是一樣的

父類有虛函數表,子類肯定也會有虛函數表,并且一個隻有一個虛函數表,在多重繼承時子類中可能會有兩個虛函數表

父類中虛函數,子類重寫函數,無論是否加virtual 都是虛函數

如果子類沒有虛函數,父類有函數,那麼子類和父類的虛函數表内容是一樣的,但是并不是一張表,是分别的兩張表,在不同位置

C++對象模型的探索

虛函數表指針是什麼時候建立的?

編譯器編譯程式時會在相應的構造函數中添加為對象的虛函數表指針(vptr)指派的代碼,等在程式運作的時候,遇到建立對象的代碼時,執行對應的構造函數,就可以給對象vptr 進行指派了

虛函數表什麼時候建立的?

編譯器在編譯期間就為每個有虛函數的類建立好了 虛函數表,不是在運作期間建立如下圖:類A在棧上建立一個指針,在堆上new 一個A 對象,該對象的的虛函數表指針指向 隻讀資料段的虛函數表,虛函數表的存放的虛函數指針指向虛函數的代碼

C++對象模型的探索

多重繼承虛函數表

一個對象,如果它的類有多個父類,則有多個虛函數表指針(注意是虛函數表指針,不是多個虛函數表)

例如:

繼續閱讀