C++中的虛函數與虛函數表

來源:互聯網
上載者:User

轉自:www.xacn.net

作者:未知

    學習 C++ 的同志不知道有沒有和我一樣遇到過這樣的困惑:C++中的虛函數到底怎麼實現的?在各種繼承關係中,虛函數表的結構到底是什麼樣的?曾經我是很想當然,可是後來在使用ATL的過程中,我發現並不是我想的那樣。大家知道,利用C++語言本身的特性進行COM編程當然是很方便的事,但是你就得隨時隨地都知道那虛函數表裡頭到底是些什麼東西。講C++文法的書沒有義務告訴你C++產生的虛函數表是什麼樣的,這就是頭痛的所在。
 
    自已做實驗是件很快樂的事,我很願意這麼做。

    首先寫個函數,作為我們實驗的基礎。傳入虛函數表指標,顯示虛數表的內容。

void DispVFT(DWORD* pVFT)
{
 
 printf("VFT Pointer:%p/n" , pVFT);
 printf("Begin/n");
 DWORD* p = (DWORD* )*pVFT;//得到VFT的首址
 while(*p) //這個地方我是看錶項是不是為空白來判斷是否到了表尾,
    //大多數情況都是對的,不過不能為準
 {
  printf("VF:%p , %p/n", p , *p);
  p++;
 }
 printf("End/n/n");
}

首先我們看單個類時的虛函數表的情況:
class C1
{
public:
 C1()
 {
  //printf("In/ C1/n");
  //DispVFT((DWORD*)this/);
 };
 virtual F1()
 {}
};
void main()
{
 C1 c1;

 //由於C1中沒有成員資料,所有我們可以用這種方式判斷
 //C1/中的虛函數表指標的個數
 printf("vftptr count :%d/n" , sizeof(C1) / 4);
 //顯示記憶體結構
 DispVFT((DWORD* )&c1);
}

輸出:
vftptr count :1
VFT Pointer:0012FF7C
Begin
VF:00420048 , 00401078(C1::F1)
End

很單純,不用多講,這是我們意料之中的結果。

下面我們進行簡單繼承的實驗

class C2 : public C1
{
public:
 C2(){
  printf("In C1/n");
  DispVFT((DWORD*)this);
 }
 virtual F2()
 {}
};

void main()
{
 C2 c2;
 C1* pC1 = &c2;
 printf("vftptr count :%d/n" , sizeof(C2) / 4);
 printf("C1/n");
 DispVFT((DWORD*)pC1);
 printf("C2/n");
 DispVFT((DWORD*)&c2);
}
輸出:
In C1
VFT Pointer:0012FF7C
Begin
VF:00420048 , 00401087(C1::F1)
End

In C2
VFT Pointer:0012FF7C
Begin
VF:004200F4 , 00401087(C1::F1) //輸出的第一項是表的首址與對應的表項內容,看看地址,與 In C1的不同,說明是不同的兩個表
VF:004200F8 , 0040108C(C2::F2)
End

vftptr count :1
C1
VFT Pointer:0012FF7C
Begin
VF:004200F4 , 00401087(C1::F1)
VF:004200F8 , 0040108C(C2::F2)
End

C2
VFT Pointer:0012FF7C
Begin
VF:004200F4 , 00401087(C1::F1)
VF:004200F8 , 0040108C(C2::F2)
End

大家可以看到最後虛函數表指標仍然是同一個,表中按順序放入了C1(基類)與C2(衍生類別)的虛函數指標。

下面是多重繼承
class C1
{
public:
 C1()
 {
  //printf("In/ C1/n");
  //DispVFT((DWORD*)this/);
 }
 virtual F1(){}
};

class C2
{
public:
 C2(){
  //printf("In/ C1/n");
  //DispVFT((DWORD*)this/);
 }
 virtual F2(){}
};
class C3 : public C1 , public C2
{
public:
 C3(){
  //printf("In/ C1/n");
  //DispVFT((DWORD*)this/);
 }
 virtual F3(){}
};

void main()
{
 C3 c3;
 C2* pC2 = &c3;
 C1* pC1 = &c3;
 printf("vftptr count :%d/n" , sizeof(C3) / 4);
 printf("C1/n");
 DispVFT((DWORD*)pC1);
 printf("C2/n");
 DispVFT((DWORD*)pC2);
 printf("C3/n");
 DispVFT((DWORD*)&c3);
}
輸出:
vftptr count :2
C1
VFT Pointer:0012FF78
Begin
VF:00420104 , 00401046(C1::F1)
VF:00420108 , 0040101E(C3::F3)
End

C2
VFT Pointer:0012FF7C
Begin
VF:00420100 , 00401028(C2::F2)
VF:00420104 , 00401046(C1::F1)
VF:00420108 , 0040101E(C3::F3)
End

C3
VFT Pointer:0012FF78
Begin
VF:00420104 , 00401046(C1::F1)
VF:00420108 , 0040101E(C3::F3)
End

虛函數表指標變成兩個了,也就是說現在是用兩個虛函數表指標維護一個表,總結一下就是,虛函數表指標的個數等於基類的個數。至於虛函數表的個數,應該是三個。你可以把我在建構函式中加的代碼去掉注釋符,看看輸出。你會發現每次輸出的表的首地址都是不一樣,那表當然也不是同一個表。

下面說說多層繼承的情況:
class C1
{
public:
 C1()
 {
  printf("In C1/n");
  DispVFT((DWORD*)this);
 }
 virtual F1(){}
};

class C2 : public C1
{
public:
 C2(){
  printf("In C2/n");
  DispVFT((DWORD*)this);
 }
 virtual F2(){}
};
class C3 : public C2
{
public:
 C3(){
  printf("In C3/n");
  DispVFT((DWORD*)this);
 }
 virtual F3(){}
};

void main()
{
 C3 c3;
 C2* pC2 = &c3;
 C1* pC1 = &c3;
 printf("vftptr count :%d/n" , sizeof(C3) / 4);
 printf("C1/n");
 DispVFT((DWORD*)pC1);
 printf("C2/n");
 DispVFT((DWORD*)pC2);
 printf("C3/n");
 DispVFT((DWORD*)&c3);
 
}

輸出:
In C1
VFT Pointer:0012FF7C
Begin
VF:00421090 , 00401046(C1::F1)
End

In C2
VFT Pointer:0012FF7C
Begin
VF:0042010C , 00401046(C1::F1) //這裡是類C2的vftable,第一個輸出是它首址與表項內容
VF:00420110 , 00401028(C2::F2)
End

In C3
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046(C1::F1)
VF:004210A0 , 00401028(C2::F2)
VF:004210A4 , 00401078(C3::F3)
End

vftptr count :1
C1
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

C2
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

C3
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

得到的結果:我們看到了虛函數表指標是一個,可是你仔細看看每個建構函式的輸出!輸出的第一項是表的首址與對應的表項。大家可以看到,首址都是不一樣的這說明是三個不同的表,那麼這個類就有三個虛函數表。你可能會想,這三個表在什麼時候用呢,事實上,在C3的執行個體被構造出來後,只有最後一個表,也就是C3的表在用,其它的表跟本就是沒有用的,C++在沒有通過你同意的情況下,在浪費你的空間(多重繼承也存在同樣的問題)。微軟想了個辦法把其它的不用的虛函數表去掉:__declspec(novtable)

class __declspec(novtable)C1
{
public:
 C1()
 {
  //printf("In/ C1/n");
  //DispVFT((DWORD*)this/); //這裡得去掉,既然沒有那個表,怎麼輸出
 }
 virtual F1(){}
};

class __declspec(novtable)C2 : public C1
{
public:
 C2(){
  //printf("In/ C2/n");
  //DispVFT((DWORD*)this);//這裡得去掉,既然沒有那個表,怎麼輸出
 }
 virtual F2(){}
};
class C3 : public C2
{
public:
 C3(){
  printf("In C3/n");
  DispVFT((DWORD*)this);
 }
 virtual F3(){}
};

void main()
{
 
 C3 c3;
 C2* pC2 = &c3;
 C1* pC1 = &c3;
 printf("vftptr count :%d/n" , sizeof(C3) / 4);
 printf("C1/n");
 DispVFT((DWORD*)pC1);
 printf("C2/n");
 DispVFT((DWORD*)pC2);
 printf("C3/n");
 DispVFT((DWORD*)&c3);
 
}

輸出:
In C3
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

vftptr count :1
C1
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

C2
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

C3
VFT Pointer:0012FF7C
Begin
VF:0042109C , 00401046
VF:004210A0 , 00401028
VF:004210A4 , 00401078
End

可以看到一切正常,只是在不知不覺中,你的程式瘦身成功,不過如果你決定要去掉類的虛函數表,你最好可以確定這個類應該是個被繼承的基類,而不是最後派生使用的類。否則可能會出錯,比如:

void func1(C1* p)
{
 p->F1();
}
void main()
{
 C2 c2;
 func1(&c2);
}

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.