天天看點

C++深入學習-對象模型對象記憶體模型的必要

對象記憶體模型的必要

    當提起int,short,long,double和float等類型時,我們已經牢記了這些類型占用記憶體大小,占用幾個位元組,表示範圍是多少,掌握了這些資訊是編寫程式最基本的要求,而且我們确實也需要知道在那種場景下用哪種類型。但是對于自定義的類型,我們很少去考慮它的記憶體占用空間和記憶體結構模型,有同學說:我不知道對象的記憶體模型,照樣能把程式寫的很好。但願事情是這樣的吧。但是我相信,知己知彼,才會百戰不殆。

空的類

class CEmpty{
};
           

    空的類對象大小不是0,是一個位元組,即用一個char來表明自己的存在。

包含靜态變量的類

class CAnimal {
public:
    CAnimal();
    ~CAnimal();
private:
    int m_member;
    static int m_stcMem;
};
           

    類的靜态變量屬于類的特性,不屬于某一個對象。是以,在對象的模型中是找不到m_stcMem的,是以CAnimal 對象的大小為4位元組,模型示意如下:

class CAnimal   size(4):
        +---
 0      | m_member
        +---
           

支援多态的類

    多态是C++的基本特性之一,我們這裡隻讨論與對象模型相關的動态屬性的多态(靜态屬性的多态為:函數重載和函數模闆)深入了解對象的模型構造才能了解這一特性實作的原理。

class CAnimal {
public:
    CAnimal() {}
    ~CAnimal() {}
public:
    virtual void Run() = 0;
private:
    int m_member;
};
           

    類的多态是由virtual函數來實作的,更确切地說是由virtual機制來實作的,當類的函數具有這種vitual特性以後,所對應的對象(雖然這個CAnimal不能執行個體化,當然不能執行個體化,你見過一個動物叫‘Animal’嗎)結構以及大小是怎樣的呢?看下圖:

//CAnimal Object
class CAnimal   size(8):
        +---
 0      | {vfptr}
 4      | m_member
        +---
//Virtual function table  
CAnimal::$vftable@:
        | &CAnimal_meta
        |  0
 0      | &CAnimal::Run
           

    可以看到:CAnimal的對象大小變成了8位元組:一個成員變量m_member(4位元組),一個vfptr指針(4位元組:一個指針,不管它指向哪一種資料類型,指針本身所占用的記憶體是固定的)。vfptr指針指向了一個CAnimal::$vftable@ 稱之為virtual function table(虛函數表)這個表存儲了CAnimal内所有virtual的函數,這個表既然不占用對象的空間,它存儲在哪裡?

    由上面對象模型可以看到,生成類對象的時候,編譯器會将類對象的前四個位元組設定為虛表的位址,而這四個位元組就是一個指向虛函數表的指針,我們可以通過這個位址找到虛函數表的位置。由此看來虛函數表屬于類,類的所有對象共享這個類的虛函數表。

    基類的對象結構就是這樣。但是,派生子類的對象内部是什麼樣的結構呢

class CAnimal {
public:
    CAnimal() {}
    ~CAnimal() {}
public:
    virtual void Run() = 0;
    virtual void Eat() = 0;
private:
    int m_member;
};
class CDog : public CAnimal {
    CDog() {}
    ~CDog() {}
public:
    void Run() {}
    void Eat() {}

    virtual void Sleep() {}
private:
    int m_dogMember;
};
           

    首先可以肯定的是,子類對象中肯定有父類的特征,子類所具有的自己“獨創”的virtual函數,應該也會加入到虛函數表中,以此支援自己後代的多态性。如下圖:

//CDog Object
class CDog      size(12):
        +---
 0      | +--- (base class CAnimal)
 0      | | {vfptr}
 4      | | m_member
        | +---
 8      | m_dogMember
        +---
//Virtual function table 
CDog::$vftable@:
        | &CDog_meta
        |  0
 0      | &CDog::Run
 1      | &CDog::Eat
 2      | &CDog::Sleep
           

    類的繼承體系當中還有一種情形是:虛繼承。這裡的“虛”,其實了解為"共享"更好一些,就是在對象模型當中,虛繼承的基類被作為共享目标而存在。下面是簡單的代碼:

class CAnimal {
public:
    CAnimal() {}
    ~CAnimal() {}
public:
    virtual void Run() = 0;
    virtual void Eat() = 0;
private:
    int m_member;
};
class CWolf : virtual public CAnimal {
public:
    CWolf() {}
    ~CWolf() {}
public:
    virtual void Run(){}
    virtual void Eat(){}
};
           

發生virtual派生之後,對象模型則是:

//CWolf Object
class CWolf     size(16):
        +---
 0      | {vbptr}
        +---
4       | (vtordisp for vbase CAnimal)
        +--- (virtual base CAnimal)
 8      | {vfptr}
12      | m_member
        +---
//Virtual base table        
CWolf::$vbtable@:
 0      | 0
 1      | 8 (CWolfd(CWolf+0)CAnimal)
//Virtual function table
CWolf::$vftable@:
        | -8
 0      | &(vtordisp) CWolf::Run
 1      | &(vtordisp) CWolf::Eat
           

    可以比較一下,虛繼承發生時,對象中多了一個vbptr,注意是vbptr(虛基類指針),不是之前說到的vfptr(虛函數表指針)。前面我們說了,virtual繼承可以了解為“共享”,"共享"的是什麼呢,當然是Base 類 。我們可以從下面這些代碼看到這樣設計的原因。.

class CAnimal {
public:
    CAnimal() {}
    ~CAnimal() {}
public:
    virtual void Run() = 0;
    virtual void Eat() = 0;
private:
    int m_member;
};
class CWolf : virtual public CAnimal {
public:
    CWolf() {}
    ~CWolf() {}
public:
    virtual void Run(){}
    virtual void Eat(){}
    virtual void WolfEat() {}
private:
    int m_wolf;
};
class CDog : virtual public  CAnimal {
public:
    CDog() {}
    ~CDog() {}
public:
    virtual void Run() {}
    virtual void Eat() {}
    virtual void DogEat() {}
private:
    int m_dog;
};
class CWolfDog : public  CWolf, public CDog{
public:
    CWolfDog() {}
    ~CWolfDog() {}
public:
    virtual void Run() {}
    virtual void Eat() {}
    void WolfDogEat() {}
};
           

    簡單來說CWolfDog 類的兩個父類CWolf 和CDog有一個共同的父類CAnimal ,這個時候我們來看CWolfDog對象中CWolf 和CDog對應的結構是怎麼表示他們共同的Parent。對象模型如下:

//CWolfDog Object
class CWolfDog  size(36):
        +---
 0      | +--- (base class CWolf)
 0      | | {vfptr}
 4      | | {vbptr}
 8      | | m_wolf
        | +---
12      | +--- (base class CDog)
12      | | {vfptr}
16      | | {vbptr}
20      | | m_dog
        | +---
        +---
24      | (vtordisp for vbase CAnimal)
        +--- (virtual base CAnimal)
28      | {vfptr}
32      | m_member
        +---
//Virtual function table
CWolfDog::[email protected]@:
        | &CWolfDog_meta
        |  0
 0      | &CWolf::WolfEat
CWolfDog::[email protected]@:
        | -12
 0      | &CDog::DogEat
//Virtual Base table
CWolfDog::[email protected]@:
 0      | -4
 1      | 24 (CWolfDogd(CWolf+4)CAnimal)
CWolfDog::[email protected]@:
 0      | -4
 1      | 12 (CWolfDogd(CDog+4)CAnimal)
//Virtual function table
CWolfDog::[email protected]@:
        | -28
 0      | &(vtordisp) CWolfDog::Run
 1      | &(vtordisp) CWolfDog::Eat
           

    确實,在CWolfDog對象中隻存在一個CAnimal的結構,但是,它的名稱變成了virtual base CAnimal,我的了解是:這是在标明這是一個共享的CAnimal結構。(我們還注意到這時的CAnimal前面還有一個修飾語vtordisp,對此MSDN給出的解釋是:虛繼承中派生類重寫了基類的虛函數,并且在構造函數或者析構函數中使用指向基類的指針調用了該函數,編譯器會為虛基類添加vtordisp域先忽略吧) 而CWolf和CDog的基類都用一個vbptr,這個指針指向了共享的CAnimal結構,以此來避免了對象調用時的選擇恐懼症(恐怖菱形二義性Ambiguous)。

繼續閱讀