C++ 虛函數(shù)表、虛函數(shù)講解

前言

近期有不少同學(xué)私信我詢(xún)問(wèn)關(guān)于C++ 虛表和虛函數(shù)的相關(guān)問(wèn)題,于是就打算寫(xiě)一篇關(guān)于C++虛函數(shù)和虛表的原理文章有助于大家更好的去理解和學(xué)習(xí)。


虛函數(shù)

概念

虛函數(shù)是一種在基類(lèi)中用virtual關(guān)鍵字聲明的函數(shù),并在一個(gè)或多個(gè)派生類(lèi)中再定義的函數(shù)。虛函數(shù)的特點(diǎn)是,只要定義一個(gè)基類(lèi)的指針,就可以指向派生類(lèi)的對(duì)象。

[注:無(wú)虛函數(shù)時(shí),遵循以下規(guī)則:C++規(guī)定,定義為基類(lèi)的指針,也能作指向派生類(lèi)的指針使用,并可以用這個(gè)指向派生類(lèi)對(duì)象的指針訪問(wèn)繼承來(lái)的基類(lèi)成員;但不能用它訪問(wèn)派生類(lèi)的成員。]

  • 使用虛函數(shù)實(shí)現(xiàn)運(yùn)行時(shí)的多態(tài)性的關(guān)鍵在于:必須通過(guò)基類(lèi)指針訪問(wèn)這些函數(shù)。

  • 一旦一個(gè)函數(shù)定義為虛函數(shù),無(wú)論它傳下去多少層,一直保持為虛函數(shù)。

  • 把虛函數(shù)的再定義稱(chēng)為過(guò)載(overriding)而不叫重載(overloading)。

  • 純虛函數(shù):是定義在基類(lèi)中的一種只給出函數(shù)原型,而沒(méi)有任何與該基類(lèi)有關(guān)的定義的函數(shù)。純虛函數(shù)使得任何派生類(lèi)都必須定義自己的函數(shù)版本。否則編譯報(bào)錯(cuò)。純虛函數(shù)定義的一般形式:

  virtual type func_name(args)=0;
  • 含有純虛函數(shù)的基類(lèi)稱(chēng)為抽象基類(lèi)。抽象基類(lèi)又一個(gè)重要特性:抽象類(lèi)不能建立對(duì)象。但是抽象基類(lèi)可以有指向自己的指針,以支持運(yùn)行時(shí)的多態(tài)性。

虛函數(shù)示例代碼

#include"test.h"
#include<iostream>
using namespace std;

class Base{

public:

    void printf()
    {
        cout << "Base printf()" << endl;
    }

    virtual void func()
    {
        cout << "Base func()" << endl;
    }

};

class Derived:public Base{

public:

    void printf()
    {
        cout << "Derived printf()" << endl;
    }

    virtual void func()
    {
        cout << "Derived func()" << endl;
    }

};

示例講解

在以上示例代碼中,我們聲明了一個(gè)父類(lèi) Base,和它的一個(gè)派生類(lèi) Derive,其中 printf() 實(shí)例方法是非虛函數(shù),而func()方法被聲明為了虛函數(shù)。并且在子類(lèi)中我們重新實(shí)現(xiàn)了printf() 和 func()方法。下面我們分別構(gòu)造出一個(gè) Derive 實(shí)例和Base 實(shí)例,分別用示例對(duì)象訪問(wèn)各func()和printf()方法。然后構(gòu)造新的Derived實(shí)例,并分別將其地址賦給 Base 指針和 Derived 指針,然后分別輸出訪問(wèn)func()和printf()方法的結(jié)果:

int main()
{
    Base baseObj = Base();
    baseObj.func();
    baseObj.printf();
    Derived derivedObj = Derived();
    derivedObj.func();
    derivedObj.printf();

    Derived* pDerivedObj = new Derived();
    Base* pBaseObj = pDerivedObj;
    pDerivedObj->func();
    pBaseObj->func();
    pDerivedObj->printf();
    pBaseObj->printf();
    delete pDerivedObj;
    return 0;
}

運(yùn)行結(jié)果

Terminal output result:

Base func()
Base printf()
Derived func()
Derived printf()

Derived func()
Derived func()
Derived printf()
Base printf()

結(jié)果描述

Base和Derived實(shí)例分別訪問(wèn)func()和printf()方法。運(yùn)行結(jié)果為各自對(duì)應(yīng)的func()和printf()方法輸出。
pDerivedObj 和 pBaseObj指針?lè)謩e指向了Derived實(shí)例的地址,對(duì)于 pDerivedObj 指針的操作表現(xiàn)出來(lái)它本身的方法輸出,然而當(dāng)我們把相同對(duì)象的地址賦給 pBaseObj 指針時(shí),可以發(fā)現(xiàn)它的非虛函數(shù)printf()竟然表現(xiàn)出了父類(lèi)的行為,并沒(méi)有被重寫(xiě)的樣子。那到底是什么原因造成了這樣的結(jié)果呢?我們繼續(xù)往下看虛函數(shù)表的介紹。

虛函數(shù)表以及內(nèi)存布局

虛函數(shù)(Virtual Function)是通過(guò)一張?zhí)摵瘮?shù)表(Virtual Table)來(lái)實(shí)現(xiàn)的。簡(jiǎn)稱(chēng)為V-Table。在這個(gè)表中,主是要一個(gè)類(lèi)的虛函數(shù)的地址表,這張表解決了繼承、覆蓋的問(wèn)題,保證其容真實(shí)反應(yīng)實(shí)際的函數(shù)。這樣,在有虛函數(shù)的類(lèi)的實(shí)例中這個(gè)表被分配在了這個(gè)實(shí)例的內(nèi)存中,所以,當(dāng)我們用父類(lèi)的指針來(lái)操作一個(gè)子類(lèi)的時(shí)候,這張?zhí)摵瘮?shù)表就顯得由為重要了,它就像一個(gè)地圖一樣,指明了實(shí)際所應(yīng)該調(diào)用的函數(shù)。

這里我們著重看一下這張?zhí)摵瘮?shù)表。C++的編譯器應(yīng)該是保證虛函數(shù)表的指針存在于對(duì)象實(shí)例中最前面的位置(這是為了保證取到虛函數(shù)表的有最高的性能——如果有多層繼承或是多重繼承的情況下)。 這意味著我們通過(guò)對(duì)象實(shí)例的地址得到這張?zhí)摵瘮?shù)表,然后就可以遍歷其中函數(shù)指針,并調(diào)用相應(yīng)的函數(shù)。

示例代碼(一下示例代碼編譯環(huán)境是X86并且采用4byte對(duì)齊)

非虛函數(shù)類(lèi)

class Base1
{
    int a;
    char c;
public:
    void CommonFunction() {};
};

內(nèi)存布局情況

  class Base1   size(8):
    +---
   0    | a
   4    | c
        | <alignment member> (size=3)
    +---
+---

博主未來(lái)為了讓同學(xué)們注意一下在類(lèi)內(nèi)存布局中常見(jiàn)的字節(jié)對(duì)齊問(wèn)題,就專(zhuān)門(mén)在Base1類(lèi)中添加了char c變量。可以很清晰的看出在內(nèi)存中a和c成員變量依據(jù)聲明的順序進(jìn)行排列(類(lèi)內(nèi)偏移為0開(kāi)始)并且有3字節(jié)用于對(duì)齊,成員函數(shù)不占內(nèi)存空間。

單繼承派生類(lèi)不含非虛函數(shù)

class DerivedClass : public Base1
{
    int c;
public:
    void DerivedCommonFunction() {};
};

內(nèi)存布局情況

  class DerivedClass    size(12):
    +---
   0    | +--- (base class Base1)
   0    | | a
   4    | | c
        | | <alignment member> (size=3)
    | +---
   8    | c
    +---

可以看到子類(lèi)DerivedClass繼承了父類(lèi)Base1的成員變量,在內(nèi)存排布上,先是排布了父類(lèi)的成員變量,接著排布子類(lèi)的成員變量,同樣,成員函數(shù)不占字節(jié)。

存在虛函數(shù)類(lèi)

class Base1
{
    int a;
    char c;
public:
    void CommonFunction() {};
    void virtual VirtualFunction() {};
};

內(nèi)存分布情況

  class Base1   size(12):
    +---
   0    | {vfptr}
   4    | a
   8    | c
        | <alignment member> (size=3)
    +---

  Base1::$vftable@:
    | &Base1_meta
    |  0
   0    | &Base1::VirtualFunction

這個(gè)內(nèi)存結(jié)構(gòu)圖分成了兩個(gè)部分,上面是內(nèi)存分布,下面是虛表,我們逐個(gè)看一下。從上圖可以看出虛表指針?lè)旁诹藘?nèi)存的開(kāi)始處(0地址偏移),然后再是成員變量;下面生成了虛表,緊跟在&Base1_meta后面的0表示,這張?zhí)摫韺?duì)應(yīng)的虛指針在內(nèi)存中的分布,下面列出了虛函數(shù),左側(cè)的0是這個(gè)虛函數(shù)的序號(hào),因?yàn)椴┲髦粚?xiě)了一個(gè)虛函數(shù),所以只有一項(xiàng),如果有多個(gè)虛函數(shù),會(huì)有序號(hào)為1,為2的虛函數(shù)列出來(lái)。

通過(guò)上面這個(gè)例子有同學(xué)就問(wèn)了虛表指針以及虛表是什么時(shí)候創(chuàng)建的呢? 構(gòu)造函數(shù)創(chuàng)建的時(shí)候即類(lèi)對(duì)象實(shí)例化的時(shí)候就創(chuàng)建的。那么如何利用虛表指針與虛表來(lái)實(shí)現(xiàn)多態(tài)的呢? 當(dāng)創(chuàng)建一個(gè)含有虛函數(shù)的父類(lèi)的對(duì)象時(shí),編譯器在對(duì)象構(gòu)造時(shí)將虛表指針指向父類(lèi)的虛函數(shù);同樣,當(dāng)創(chuàng)建子類(lèi)的對(duì)象時(shí),編譯器在構(gòu)造函數(shù)里將虛表指針(子類(lèi)只有一個(gè)虛表指針,它來(lái)自父類(lèi))指向子類(lèi)的虛表(這個(gè)虛表里面的虛函數(shù)入口地址是子類(lèi)的)從而可以實(shí)現(xiàn)多態(tài)。

單繼承派生類(lèi)中也有虛函數(shù)并且存在覆蓋繼承

class DerivedClass : public Base1
{
    int d;
public:
    void DerivedCommonFunction() {};
    void virtual VirtualFunction() {};
};

內(nèi)存分布情況

  class DerivedClass    size(16):
    +---
   0    | +--- (base class Base1)
   0    | | {vfptr}
   4    | | a
   8    | | c
        | | <alignment member> (size=3)
    | +---
  12    | d
    +---

  DerivedClass::$vftable@:
    | &DerivedClass_meta
    |  0
   0    | &DerivedClass::VirtualFunction

上半部是內(nèi)存分布,可以看到,虛表指針被繼承了,且仍位于內(nèi)存排布的起始處,下面是父類(lèi)的成員變量a和c,最后是子類(lèi)的成員變量d,注意虛表指針只有一個(gè),子類(lèi)并沒(méi)有再生成虛表指針了;下半部的虛表情況與父類(lèi)是一樣的由于子類(lèi)將父類(lèi)的虛函數(shù)方法重寫(xiě)了即產(chǎn)生的虛表序號(hào)只有一個(gè)。

單繼承派生類(lèi)中也有虛函數(shù)并且不存在覆蓋繼承

class Base1
{
    int a;
    char c;
public:
    void CommonFunction() {};
    void virtual VirtualFunction() {};
};

class DerivedClass : public Base1
{
    int d;
public:
    void DerivedCommonFunction() {};
    void virtual VirtualFunction1() {};
};

內(nèi)存布局情況

  class DerivedClass    size(16):
    +---
   0    | +--- (base class Base1)
   0    | | {vfptr}
   4    | | a
   8    | | c
        | | <alignment member> (size=3)
    | +---
  12    | d
    +---

  DerivedClass::$vftable@:
    | &DerivedClass_meta
    |  0
   0    | &Base1::VirtualFunction
   1    | &DerivedClass::VirtualFunction1

此種情況內(nèi)存分布中上半部分也只有一個(gè)虛表指針變量?jī)?nèi)存分布依次排列,但是下方虛表的內(nèi)容變化了,虛表的0號(hào)是父類(lèi)的VirtualFunction,而1號(hào)放的是子類(lèi)的VirtualFunction2。也就是說(shuō),如果定義了DerivedClass的對(duì)象,那么在構(gòu)造時(shí),虛表指針就會(huì)指向這個(gè)虛表,以后如果調(diào)用的是VirtualFunction,那么會(huì)從父類(lèi)中尋找對(duì)應(yīng)的虛函數(shù),如果調(diào)用的是VirtualFunction1,那么會(huì)從子類(lèi)中尋找對(duì)應(yīng)的虛函數(shù)。

單繼承派生類(lèi)中即存在覆蓋虛函數(shù)也存在非覆蓋虛函數(shù)繼承

class Base1
{
    int a;
    char c;
public:
    void CommonFunction() {};
    void virtual VirtualFunction() {};
};

class DerivedClass : public Base1
{
    int c;
public:
    void DerivedCommonFunction() {};
    void virtual VirtualFunction() {};
    void virtual VirtualFunction1() {};
};

內(nèi)存布局情況

  class DerivedClass    size(16):
    +---
   0    | +--- (base class Base1)
   0    | | {vfptr}
   4    | | a
   8    | | c
        | | <alignment member> (size=3)
    | +---
  12    | c
    +---

  DerivedClass::$vftable@:
    | &DerivedClass_meta
    |  0
   0    | &DerivedClass::VirtualFunction
   1    | &DerivedClass::VirtualFunction1

根據(jù)上面的內(nèi)存布局情況,我們既重寫(xiě)了父類(lèi)的虛函數(shù),也有新添的虛函數(shù),最終虛函數(shù)表0號(hào)和1號(hào)都是子類(lèi)對(duì)應(yīng)的虛函數(shù)地址。

多繼承派生類(lèi)中存在覆蓋虛函數(shù)繼承

class Base1
{
    int a;
    char c;
public:
    void CommonFunction() {};
    void virtual VirtualFunction() {};
};

class DerivedClass1 : public Base1
{
    int b;
public:
    void DerivedCommonFunction() {};
    void virtual VirtualFunction() {};
};

class DerivedClass2 : public Base1
{
    int d;
public:
    void DerivedCommonFunction() {};
    void virtual VirtualFunction() {};
};

class DerivedDerivedClass : public DerivedClass1, public DerivedClass2
{
    int e;
public:
    void DerivedDerivedCommonFunction() {};
    void virtual VirtualFunction() {};
};

內(nèi)存布局

class Base1 size(12):
+---
0   | {vfptr}
4   | a
8   | c
    | <alignment member> (size=3)
+---

Base1::$vftable@:
| &Base1_meta
|  0
0   | &Base1::VirtualFunction

class DerivedClass1 size(16):
    +---
   0    | +--- (base class Base1)
   0    | | {vfptr}
   4    | | a
   8    | | c
        | | <alignment member> (size=3)
    | +---
  12    | b
    +---

  DerivedClass1::$vftable@:
    | &DerivedClass1_meta
    |  0
   0    | &DerivedClass1::VirtualFunction

  DerivedClass1::VirtualFunction this adjustor: 0

  class DerivedClass2   size(16):
    +---
   0    | +--- (base class Base1)
   0    | | {vfptr}
   4    | | a
   8    | | c
        | | <alignment member> (size=3)
    | +---
  12    | d
    +---

  DerivedClass2::$vftable@:
    | &DerivedClass2_meta
    |  0
   0    | &DerivedClass2::VirtualFunction

  DerivedClass2::VirtualFunction this adjustor: 0

  class DerivedDerivedClass size(36):
    +---
   0    | +--- (base class DerivedClass1)
   0    | | +--- (base class Base1)
   0    | | | {vfptr}
   4    | | | a
   8    | | | c
        | | | <alignment member> (size=3)
    | | +---
  12    | | b
    | +---
  16    | +--- (base class DerivedClass2)
  16    | | +--- (base class Base1)
  16    | | | {vfptr}
  20    | | | a
  24    | | | c
        | | | <alignment member> (size=3)
    | | +---
  28    | | d
    | +---
  32    | e
    +---

  DerivedDerivedClass::$vftable@DerivedClass1@:
    | &DerivedDerivedClass_meta
    |  0
   0    | &DerivedDerivedClass::VirtualFunction

  DerivedDerivedClass::$vftable@DerivedClass2@:
    | -16
   0    | &thunk: this-=16; goto DerivedDerivedClass::VirtualFunction

根據(jù)上面的內(nèi)存分布情況,此多繼承覆蓋情況,我分別把每個(gè)類(lèi)的內(nèi)存分布都打了出來(lái),下面我們重點(diǎn)看看這個(gè)類(lèi)DerivedDerivedClass,由外向內(nèi)看,它并列地排布著繼承而來(lái)的兩個(gè)父類(lèi)DerivedClass1與DerivedClass2,還有自身的成員變量e。DerivedClass1包含了它的成員變量b,以及Base1,Base1有一個(gè)0地址偏移的虛表指針,然后是成員變量a和c;DerivedClass2的內(nèi)存排布類(lèi)似于DerivedClass1,注意到DerivedClass2里面竟然也有一份Base1。
我們?cè)賮?lái)看看虛表繼承情況,我們看到了有兩份虛表了,分別針對(duì)DerivedClass1與DerivedClass2,在&DerivedDericedClass_meta下方的數(shù)字是首地址偏移量0也是DerivedClass1中的{vfptr}虛函數(shù)指針在DerivedDerivedClass的內(nèi)存偏移,靠下面的虛表的那個(gè)-16表示指向這個(gè)虛表的虛指針的內(nèi)存偏移,這正是DerivedClass2中的{vfptr}在DerivedDerivedClass的內(nèi)存偏移。

DerivedDerivedClass()的虛表的VirtualFunction()指針
[站外圖片上傳中...(image-a847ce-1565342394654)]

多繼承派生類(lèi)中不存在覆蓋虛函數(shù)繼承

class Base1
{
    int a;
    char c;
public:
    void CommonFunction() {};
    void virtual VirtualFunction() {};
};

class DerivedClass1 : public Base1
{
    int b;
public:
    void DerivedCommonFunction() {};
    void virtual VirtualFunction1() {};
};

class DerivedClass2 : public Base1
{
    int d;
public:
    void DerivedCommonFunction() {};
    void virtual VirtualFunction2() {};
};

class DerivedDerivedClass : public DerivedClass1, public DerivedClass2
{
    int e;
public:
    void DerivedDerivedCommonFunction() {};
    void virtual VirtualFunction3() {};
};

內(nèi)存布局情況

    class Base1 size(12):
    +---
    0   | {vfptr}
    4   | a
    8   | c
        | <alignment member> (size=3)
    +---

    Base1::$vftable@:
    | &Base1_meta
    |  0
    0   | &Base1::VirtualFunction

  class DerivedClass1   size(16):
    +---
   0    | +--- (base class Base1)
   0    | | {vfptr}
   4    | | a
   8    | | c
        | | <alignment member> (size=3)
    | +---
  12    | b
    +---

  DerivedClass1::$vftable@:
    | &DerivedClass1_meta
    |  0
   0    | &Base1::VirtualFunction
   1    | &DerivedClass1::VirtualFunction1

  DerivedClass1::VirtualFunction1 this adjustor: 0

  class DerivedClass2   size(16):
    +---
   0    | +--- (base class Base1)
   0    | | {vfptr}
   4    | | a
   8    | | c
        | | <alignment member> (size=3)
    | +---
  12    | d
    +---

  DerivedClass2::$vftable@:
    | &DerivedClass2_meta
    |  0
   0    | &Base1::VirtualFunction
   1    | &DerivedClass2::VirtualFunction2

  DerivedClass2::VirtualFunction2 this adjustor: 0

  class DerivedDerivedClass size(36):
    +---
   0    | +--- (base class DerivedClass1)
   0    | | +--- (base class Base1)
   0    | | | {vfptr}
   4    | | | a
   8    | | | c
        | | | <alignment member> (size=3)
    | | +---
  12    | | b
    | +---
  16    | +--- (base class DerivedClass2)
  16    | | +--- (base class Base1)
  16    | | | {vfptr}
  20    | | | a
  24    | | | c
        | | | <alignment member> (size=3)
    | | +---
  28    | | d
    | +---
  32    | e
    +---

  DerivedDerivedClass::$vftable@DerivedClass1@:
    | &DerivedDerivedClass_meta
    |  0
   0    | &Base1::VirtualFunction
   1    | &DerivedClass1::VirtualFunction1
   2    | &DerivedDerivedClass::VirtualFunction3

  DerivedDerivedClass::$vftable@DerivedClass2@:
    | -16
   0    | &Base1::VirtualFunction
   1    | &DerivedClass2::VirtualFunction2

此種情況的內(nèi)存分布和覆蓋多繼承一樣,唯一注意的就是在多繼承中成員虛函數(shù)地址會(huì)保存到第一個(gè)繼承父類(lèi)的虛函數(shù)表。

多繼承之虛繼承派生類(lèi)中存在覆蓋虛函數(shù)繼承

class Base1
{
    int a;
    char c;
public:
    void CommonFunction() {};
    void virtual VirtualFunction() {};
};

class DerivedClass1 : virtual public Base1
{
    int b;
public:
    void DerivedCommonFunction() {};
    void virtual VirtualFunction1() {};
};

class DerivedClass2 : virtual public Base1
{
    int d;
public:
    void DerivedCommonFunction() {};
    void virtual VirtualFunction2() {};
};

class DerivedDerivedClass : public DerivedClass1, public DerivedClass2
{
    int e;
public:
    void DerivedDerivedCommonFunction() {};
    void virtual VirtualFunction3() {};
};

內(nèi)存分布情況

    class Base1 size(12):
    +---
    0   | {vfptr}
    4   | a
    8   | c
        | <alignment member> (size=3)
    +---

    Base1::$vftable@:
    | &Base1_meta
    |  0
    0   | &Base1::VirtualFunction

  class DerivedClass1   size(24):
    +---
   0    | {vfptr}
   4    | {vbptr}
   8    | b
    +---
    +--- (virtual base Base1)
  12    | {vfptr}
  16    | a
  20    | c
        | <alignment member> (size=3)
    +---

  DerivedClass1::$vftable@DerivedClass1@:
    | &DerivedClass1_meta
    |  0
   0    | &DerivedClass1::VirtualFunction1

  DerivedClass1::$vbtable@:
   0    | -4
   1    | 8 (DerivedClass1d(DerivedClass1+4)Base1)

  DerivedClass1::$vftable@Base1@:
    | -12
   0    | &Base1::VirtualFunction

  DerivedClass1::VirtualFunction1 this adjustor: 0
  vbi:     class  offset o.vbptr  o.vbte fVtorDisp
             Base1      12       4       4 0

  class DerivedClass2   size(24):
    +---
   0    | {vfptr}
   4    | {vbptr}
   8    | d
    +---
    +--- (virtual base Base1)
  12    | {vfptr}
  16    | a
  20    | c
        | <alignment member> (size=3)
    +---

  DerivedClass2::$vftable@DerivedClass2@:
    | &DerivedClass2_meta
    |  0
   0    | &DerivedClass2::VirtualFunction2

  DerivedClass2::$vbtable@:
   0    | -4
   1    | 8 (DerivedClass2d(DerivedClass2+4)Base1)

  DerivedClass2::$vftable@Base1@:
    | -12
   0    | &Base1::VirtualFunction

  DerivedClass2::VirtualFunction2 this adjustor: 0
  vbi:     class  offset o.vbptr  o.vbte fVtorDisp
             Base1      12       4       4 0

  class DerivedDerivedClass size(40):
    +---
   0    | +--- (base class DerivedClass1)
   0    | | {vfptr}
   4    | | {vbptr}
   8    | | b
    | +---
  12    | +--- (base class DerivedClass2)
  12    | | {vfptr}
  16    | | {vbptr}
  20    | | d
    | +---
  24    | e
    +---
    +--- (virtual base Base1)
  28    | {vfptr}
  32    | a
  36    | c
        | <alignment member> (size=3)
    +---

  DerivedDerivedClass::$vftable@DerivedClass1@:
    | &DerivedDerivedClass_meta
    |  0
   0    | &DerivedClass1::VirtualFunction1
   1    | &DerivedDerivedClass::VirtualFunction3

  DerivedDerivedClass::$vftable@DerivedClass2@:
    | -12
   0    | &DerivedClass2::VirtualFunction2

  DerivedDerivedClass::$vbtable@DerivedClass1@:
   0    | -4
   1    | 24 (DerivedDerivedClassd(DerivedClass1+4)Base1)

  DerivedDerivedClass::$vbtable@DerivedClass2@:
   0    | -4
   1    | 12 (DerivedDerivedClassd(DerivedClass2+4)Base1)

  DerivedDerivedClass::$vftable@Base1@:
    | -28
   0    | &Base1::VirtualFunction

上面虛繼承的內(nèi)存分布不做過(guò)多的敘述,下來(lái)總結(jié)一下:
虛繼承的作用是減少了對(duì)基類(lèi)的重復(fù)(在一般多繼承中會(huì)造成二義性編譯時(shí)出錯(cuò),虛繼承可以消除二義性),但是代價(jià)是增加了虛表指針的負(fù)擔(dān)(更多的虛表指針)。根據(jù)以上示例當(dāng)基類(lèi)有虛函數(shù)時(shí):

  • 1 每個(gè)類(lèi)都有虛指針和虛表;

  • 2 如果不是虛繼承,那么子類(lèi)將父類(lèi)的虛指針繼承下來(lái),并指向自身的虛表(發(fā)生在對(duì)象構(gòu)造時(shí))。有多少個(gè)虛函數(shù),虛表里面的項(xiàng)就會(huì)有多少。多重繼承時(shí),可能存在多個(gè)的基類(lèi)虛表與虛指針;

  • 3 如果是虛繼承,那么子類(lèi)會(huì)有兩份虛指針,一份指向自己的虛表,另一份指向虛基表,多重繼承時(shí)虛基表與虛基表指針有且只有一份。

博客著作權(quán)歸本作者所有,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,885評(píng)論 6 541
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,312評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,993評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,667評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,410評(píng)論 6 411
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,778評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評(píng)論 3 446
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,955評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,521評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,266評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,468評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,696評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,095評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,385評(píng)論 1 294
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,193評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,431評(píng)論 2 378

推薦閱讀更多精彩內(nèi)容

  • 1. 析構(gòu)函數(shù)和虛析構(gòu)函數(shù) 如果基類(lèi)的析構(gòu)函數(shù)是虛的,那么它的派生類(lèi)的析構(gòu)函數(shù)都是虛的 這將導(dǎo)致:當(dāng)派生類(lèi)析構(gòu)的時(shí)...
    杰倫哎呦哎呦閱讀 2,495評(píng)論 0 2
  • C++虛函數(shù) C++虛函數(shù)是多態(tài)性實(shí)現(xiàn)的重要方式,當(dāng)某個(gè)虛函數(shù)通過(guò)指針或者引用調(diào)用時(shí),編譯器產(chǎn)生的代碼直到運(yùn)行時(shí)才...
    小白將閱讀 1,752評(píng)論 4 19
  • 幾種語(yǔ)言的特性 匯編程序:將匯編語(yǔ)言源程序翻譯成目標(biāo)程序編譯程序:將高級(jí)語(yǔ)言源程序翻譯成目標(biāo)程序解釋程序:將高級(jí)語(yǔ)...
    囊螢映雪的螢閱讀 2,924評(píng)論 1 5
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類(lèi)型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,125評(píng)論 1 32
  • 二十.多態(tài)與虛函數(shù) 多態(tài):多態(tài)按字面的意思就是多種形態(tài)。當(dāng)類(lèi)之間存在層次結(jié)構(gòu),并且類(lèi)之間是通過(guò)繼承關(guān)聯(lián)時(shí),就會(huì)用到...
    b83dcb2e8b71閱讀 608評(píng)論 0 1