對象記憶體模型的必要
當提起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)。