自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

C++ 虛函數(shù)表剖析

開發(fā) 后端
本文介紹虛函數(shù)表是如何實(shí)現(xiàn)動態(tài)綁定的。來看一下吧。

一、概述

為了實(shí)現(xiàn)C++的多態(tài),C++使用了一種動態(tài)綁定的技術(shù)。這個技術(shù)的核心是虛函數(shù)表(下文簡稱虛表)。

二、類的虛表

每個包含了虛函數(shù)的類都包含一個虛表。

我們知道,當(dāng)一個類(A)繼承另一個類(B)時,類A會繼承類B的函數(shù)的調(diào)用權(quán)。所以如果一個基類包含了虛函數(shù),那么其繼承類也可調(diào)用這些虛函數(shù),換句話說,一個類繼承了包含虛函數(shù)的基類,那么這個類也擁有自己的虛表。

我們來看以下的代碼。類A包含虛函數(shù)vfunc1,vfunc2,由于類A包含虛函數(shù),故類A擁有一個虛表。

class A {
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int m_data1, m_data2;
};

類A的虛表如圖1所示。

圖1:類A的虛表示意圖

虛表是一個指針數(shù)組,其元素是虛函數(shù)的指針,每個元素對應(yīng)一個虛函數(shù)的函數(shù)指針。需要指出的是,普通的函數(shù)即非虛函數(shù),其調(diào)用并不需要經(jīng)過虛表,所以虛表的元素并不包括普通函數(shù)的函數(shù)指針。

虛表內(nèi)的條目,即虛函數(shù)指針的賦值發(fā)生在編譯器的編譯階段,也就是說在代碼的編譯階段,虛表就可以構(gòu)造出來了。

三、虛表指針

虛表是屬于類的,而不是屬于某個具體的對象,一個類只需要一個虛表即可。同一個類的所有對象都使用同一個虛表。

為了指定對象的虛表,對象內(nèi)部包含一個虛表的指針,來指向自己所使用的虛表。為了讓每個包含虛表的類的對象都擁有一個虛表指針,編譯器在類中添加了一個指針,*__vptr,用來指向虛表。這樣,當(dāng)類的對象在創(chuàng)建時便擁有了這個指針,且這個指針的值會自動被設(shè)置為指向類的虛表。

圖2:對象與它的虛表

上面指出,一個繼承類的基類如果包含虛函數(shù),那個這個繼承類也有擁有自己的虛表,故這個繼承類的對象也包含一個虛表指針,用來指向它的虛表。

四、動態(tài)綁定

說到這里,大家一定會好奇C++是如何利用虛表和虛表指針來實(shí)現(xiàn)動態(tài)綁定的。我們先看下面的代碼。

class A {
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
private:
int m_data1, m_data2;
};
class B : public A {
public:
virtual void vfunc1();
void func1();
private:
int m_data3;
};
class C: public B {
public:
virtual void vfunc2();
void func2();
private:
int m_data1, m_data4;
};

類A是基類,類B繼承類A,類C又繼承類B。類A,類B,類C,其對象模型如下圖3所示。

圖3:類A,類B,類C的對象模型

由于這三個類都有虛函數(shù),故編譯器為每個類都創(chuàng)建了一個虛表,即類A的虛表(A vtbl),類B的虛表(B vtbl),類C的虛表(C vtbl)。類A,類B,類C的對象都擁有一個虛表指針,*__vptr,用來指向自己所屬類的虛表。

類A包括兩個虛函數(shù),故A vtbl包含兩個指針,分別指向A::vfunc1()和A::vfunc2()。

類B繼承于類A,故類B可以調(diào)用類A的函數(shù),但由于類B重寫了B::vfunc1()函數(shù),故B vtbl的兩個指針分別指向B::vfunc1()和A::vfunc2()。

類C繼承于類B,故類C可以調(diào)用類B的函數(shù),但由于類C重寫了C::vfunc2()函數(shù),故C vtbl的兩個指針分別指向B::vfunc1()(指向繼承的最近的一個類的函數(shù))和C::vfunc2()。

雖然圖3看起來有點(diǎn)復(fù)雜,但是只要抓住“對象的虛表指針用來指向自己所屬類的虛表,虛表中的指針會指向其繼承的最近的一個類的虛函數(shù)”這個特點(diǎn),便可以快速將這幾個類的對象模型在自己的腦海中描繪出來。

非虛函數(shù)的調(diào)用不用經(jīng)過虛表,故不需要虛表中的指針指向這些函數(shù)。

假設(shè)我們定義一個類B的對象bObject。由于bObject是類B的一個對象,故bObject包含一個虛表指針,指向類B的虛表。

int main() {  
B bObject;
}

現(xiàn)在,我們聲明一個類A的指針p來指向?qū)ο骲Object。雖然p是基類的指針只能指向基類的部分,但是虛表指針亦屬于基類部分,所以p可以訪問到對象bObject的虛表指針。bObject的虛表指針指向類B的虛表,所以p可以訪問到B vtbl。如圖3所示。

int main()    
{
B bObject;
A *p = & bObject;
}

當(dāng)我們使用p來調(diào)用vfunc1()函數(shù)時,會發(fā)生什么現(xiàn)象?

int main()
{
B bObject;
A *p = & bObject;
p->vfunc1();
}

程序在執(zhí)行p->vfunc1()時,會發(fā)現(xiàn)p是個指針,且調(diào)用的函數(shù)是虛函數(shù),接下來便會進(jìn)行以下的步驟。

首先,根據(jù)虛表指針p->__vptr來訪問對象bObject對應(yīng)的虛表。雖然指針p是基類A*類型,但是*__vptr也是基類的一部分,所以可以通過p->__vptr可以訪問到對象對應(yīng)的虛表。

然后,在虛表中查找所調(diào)用的函數(shù)對應(yīng)的條目。由于虛表在編譯階段就可以構(gòu)造出來了,所以可以根據(jù)所調(diào)用的函數(shù)定位到虛表中的對應(yīng)條目。對于p->vfunc1()的調(diào)用,B vtbl的第一項(xiàng)即是vfunc1對應(yīng)的條目。

最后,根據(jù)虛表中找到的函數(shù)指針,調(diào)用函數(shù)。從圖3可以看到,B vtbl的第一項(xiàng)指向B::vfunc1(),所以p->vfunc1()實(shí)質(zhì)會調(diào)用B::vfunc1()函數(shù)。

如果p指向類A的對象,情況又是怎么樣?

int main()  
{
A aObject;
A *p = &aObject;
p->vfunc1();
}

當(dāng)aObject在創(chuàng)建時,它的虛表指針__vptr已設(shè)置為指向A vtbl,這樣p->__vptr就指向A vtbl。vfunc1在A vtbl對應(yīng)在條目指向了A::vfunc1()函數(shù),所以p->vfunc1()實(shí)質(zhì)會調(diào)用A::vfunc1()函數(shù)。

可以把以上三個調(diào)用函數(shù)的步驟用以下表達(dá)式來表示:

(*(p->__vptr)[n])(p)

可以看到,通過使用這些虛函數(shù)表,即使使用的是基類的指針來調(diào)用函數(shù),也可以達(dá)到正確調(diào)用運(yùn)行中實(shí)際對象的虛函數(shù)。

我們把經(jīng)過虛表調(diào)用虛函數(shù)的過程稱為動態(tài)綁定,其表現(xiàn)出來的現(xiàn)象稱為運(yùn)行時多態(tài)。動態(tài)綁定區(qū)別于傳統(tǒng)的函數(shù)調(diào)用,傳統(tǒng)的函數(shù)調(diào)用我們稱之為靜態(tài)綁定,即函數(shù)的調(diào)用在編譯階段就可以確定下來了。

那么,什么時候會執(zhí)行函數(shù)的動態(tài)綁定?這需要符合以下三個條件。

  •  通過指針來調(diào)用函數(shù)
  •  指針upcast向上轉(zhuǎn)型(繼承類向基類的轉(zhuǎn)換稱為upcast,關(guān)于什么是upcast,可以參考本文的參考資料)
  •  調(diào)用的是虛函數(shù)

如果一個函數(shù)調(diào)用符合以上三個條件,編譯器就會把該函數(shù)調(diào)用編譯成動態(tài)綁定,其函數(shù)的調(diào)用過程走的是上述通過虛表的機(jī)制。

五、總結(jié)

封裝,繼承,多態(tài)是面向?qū)ο笤O(shè)計的三個特征,而多態(tài)可以說是面向?qū)ο笤O(shè)計的關(guān)鍵。C++通過虛函數(shù)表,實(shí)現(xiàn)了虛函數(shù)與對象的動態(tài)綁定,從而構(gòu)建了C++面向?qū)ο蟪绦蛟O(shè)計的基石。

參考資料

  •  《C++ Primer》第三版,中文版,潘愛民等譯
  •  http://www.learncpp.com/cpp-tutorial/125-the-virtual-table/
  •  侯捷《C++最佳編程實(shí)踐》視頻,極客班,2015
  •  Upcasting and Downcasting, http://www.bogotobogo.com/cplusplus/upcasting_downcasting.php
責(zé)任編輯:龐桂玉 來源: C語言與C++編程
相關(guān)推薦

2010-01-18 17:38:54

C++虛函數(shù)表

2010-02-01 11:22:09

C++虛函數(shù)

2010-01-13 11:14:06

C++虛表

2021-03-29 07:40:32

Swift Hook 虛函數(shù)表

2011-05-24 16:20:27

虛函數(shù)

2010-01-27 13:38:29

C++ Sum函數(shù)

2010-02-06 16:39:45

C++ assert(

2010-01-20 18:06:06

C++虛基類

2024-01-23 10:13:57

C++虛函數(shù)

2024-12-17 12:00:00

C++對象模型

2024-04-22 13:22:00

虛函數(shù)象編程C++

2010-01-27 10:36:54

C++虛函數(shù)

2011-05-24 16:30:35

虛函數(shù)

2024-12-19 14:42:15

C++內(nèi)存泄漏內(nèi)存管理

2010-02-05 13:35:19

C++虛析構(gòu)函數(shù)

2010-01-14 17:42:47

CC++

2010-01-18 13:54:28

函數(shù)

2010-01-28 16:31:54

C++類型

2010-01-13 11:27:06

C++安全性

2011-07-20 17:04:55

C++虛函數(shù)動態(tài)聯(lián)編
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號