先看一例子: class base
{
public:
virtual void f() const {};
};
class d1 : virtual public base
{};
class d2 : virtual public base
{};
class derived : public d1, public d2
{};
using namespace std;
int main()
{
d1 b;
d2 c;
derived d;
cout<<sizeof(b)<<endl;
cout<<sizeof(c)<<endl;
cout<<sizeof(d)<<endl;
return 0;
} 結果是 8, 8 12.先說class Base吧,它有一個虛函數,且沒有父類,所以只有一個指標大小,也就是4BYTES了.至於d1和d2就不那麼簡單了,它不僅有虛函數而且是虛繼承.要複雜一些了.D1之所以為8是由於它有兩個表指標組成的.一個PVBTBL,一個是PVFTBL.PVBTBL是指虛基類表指標,一個類可以有0-n個VBTBL(VIRTUAL BASE CLASS TABLE),在這裡只有一個,不過很快就會看到有兩個VBTBL的類了。PVFTBL是指虛函數表指標了,一個類可以有 0-N個VFTBL(VIRTUAL FUNCTION TABLE),在這裡也只有一個。我們看一看D1的布局吧。D1---------------PVBTBL ---------------------》指向一個虛基類表。--------------PVFTBL ---------------------》指向一個虛函數表。--------------所以其大小就為8了。再說說其初始化過程。我們知道任何一個類的虛基類的建構函式是最先被調用。最開如,編譯器會把d1::vbtbl的指標表放在圖中PVBTBL的地方,此時THIS指標也指向PVBTBL,然後調整THIS指標令其指向PVFTBL,並調用BASE::BASE();BASE()會在PVFTBL的地方放上OFFSET BASE::VFTBL,由於它沒有資料成員要初始化,所以它就返回了。呵呵,很不幸,它很快就要被D1::的VFTBL所代替了,這也正是為什麼C++會根據實際對象的類型所進行正確行為的原因。不過,這裡的替代有點兒特別。它先查看,VBTABL(其中第二項為虛基類的位移[PVBTABL+4]),找到虛基類的位移量,然後再加上THIS指標的值,些時它正好指向PVFTBL,它就用D1::VFTBL替代了虛基類的虛函數表了。 上邊提到有的類會有兩個虛基表,-DEPRIVED類就是這種情況了。它的初始化比上面所說的更複雜。先看布局也再說吧----------PVBTBL ------》同上----------PVBTBL ------》同上----------PVFTBL--------》同上----------PVFTBL--------》同上。---------------------很明顯可以看到它有兩個虛基類表,ONE FOR D1,ANOTHER FOR D2。虛基類表的個數取決於當前的繼承方式及其基類的情況。在這裡可以看到,它的基類D1,D2都有虛繼承的情況,所以這裡就有兩個虛基類表了。INIT:在這裡編譯器會先準備兩個VBTBL,然後,當初始化時(也就是調用CONSTRUCTOR時),兩個VBTBL分別放到相應的位置,然後調整THIS指標的值,THIS+8,POINTING TO THE FIRST PVFTBL的位置,然後調用BASE::BASE(),可以看到的確是虛基類的建構函式是最先調用的。它會把它的OFFSET BASE::VFTBL,放到this + 8 的位置,沒有其它要初始化的成員,SO,RETURN TO THE CALLER。THEN THIS= THIS -8,INVOKE THE D1::D1(),ENTER THE D1::D1(),FIRSTLY ,IT LOOKS UP THE VBTBL POINTED BY FIRST PVTBL FOR THE OFFSET THE ITSELF,,然後,修改THIS的值,令其指向,SUBOBJECT OF D1 ,由於這裡也沒有成員,所以初始化也很簡單,它只是用它自己的虛函數替代上面有BASE::BASE()所放置的虛函數表,然後,返回。至於後面的D2,也和這裡類似,不過THIS指標所指的位置不同,它指向中的最後一個位置。終於到最後一步了,DEPRIVED會用它自己的虛函數表去替代原來已有的。。。。。(反正自己看,不用說太清了。)下面還有更好玩的。一個類可能會有多個虛函數表,且不同的。看這個例子。#include "stdafx.h"
#include <string.h>
class Base1
{
public:
virtual void Base1Test() {printf("this is from Base1 virtual function /n");}
int a;};class Base2
{
public:
virtual void Base2Test() {printf("this is from the Base2 virtual function/n");}
int b;
};class Deprived : public virtual Base1, public Base2
{
public:
int d;
};Base1 * base1;
Base2 *base2;
void f()
{Deprived deprived;base1 = &deprived;
base2 = &deprived;base1->Base1Test();base2->Base2Test();deprived.a = 16;
deprived.b = 32;
deprived.d = 48;return;}
int main(int argc, char* argv[])
{ printf("sizeof(Deprived):%d/n", sizeof(Deprived)); f();
return 0;
}NOTE:本例中,只有一個VBTBL,呵呵,由於DEPRIVED VIRTURE INHERITS THE BASE1,但是它卻有多個虛函數表。ONE FOR BASE1, ANOTHER FOR BASE2。它的記憶體布局和上面有較大變化了。如下所示。DEPRIVE ---------------- BASE2 PARTY =========》 INCLUDE 一個虛函數表指標及int b(它就是BASE2的成員變數。----------------VBTBL PARTY ==========》就是一個指向虛基類表的指標了。不用多說了。-----------------DEPRIVE PARTY ==========》這裡僅有一個DEPRIVED的成員變數d,它和BASE2共用一個虛函數表指標。所以就只有一個成員變數D了。-----------------VIRTUAL BASE PARTY -=======》也就是BASE1 PARTY了。它結構如下。 =========== PVFTBL --------------》BASE1的VIRTUAL FUNC TBL =============------------------------------- 理所當然它會所調用BASE1的CONSTRUCTOR,(FOR THE VIRTUAL BASE REASON。。。。),,然後正常初始化,不過到了最後,會比較獨特,DPRIVED CLASS有兩個VIRTUAL TABLE。一個是為BASE1準備的,一個是為BASE2準備的。也正是為什麼可以用基類的指標正確的調用子類的VIRTURL FUNCTION的原因。。。。。更詳細,就不說了,反正是自己看。呵呵。