[C++基礎]多態虛函數表詳解

C++相對其他面嚮對象語言來說,之所以靈活、高效。很大程度的佔比在於其多態技術和模板技術。C++虛函數表是支撐C++多態的重要技術,它是C++動態綁定技術的核心。

多態對象創建及內存分佈

假設有一個基類ClassA,一個繼承了該基類的派生類ClassB,並且基類中有虛函數,派生類實現了基類的虛函數。
我們在代碼中運用多態這個特性時,通常以以下兩種方式來使用:
(1) ClassA *a = new ClassB();
(2) ClassB b; ClassA *a = &b;

以上兩種方式都是用基類指針去指向一個派生類實例,區別在於第1個用了new關鍵字而分配在堆上,第2個分配在棧上。


請看上圖,不同兩種創建方式僅僅影響了派生類對象實例存在的位置。如左圖ClassA *a是一個棧上的指針。該指針指向一個在堆上實例化的子類對象。
基類如果存在虛函數,那麼在子類對象中,除了成員函數與成員變量外,編譯器會自動生成一個指向**該類的虛函數表(這裏是類ClassB)**的指針,叫作虛函數表指針。通過虛函數表指針,父類指針即可調用該虛函數表中所有的虛函數。

類的虛函數表與類實例的虛函數指針

如果一個類中有虛函數,那麼該類就有一個虛函數表。這個虛函數表是屬於類的,所有該類的實例化對象中都會有一個虛函數表指針去指向該類的虛函數表。
從上圖我們也能看到,一個類的實例要麼在堆上,要麼在棧上。也就是說一個類可以多很多個實例。但是!一個類只能有一個虛函數表。在編譯時,一個類的虛函數表就確定了,這也是爲什麼它放在了只讀數據段中。

多重繼承下的虛函數表(多次單繼承)

class ClassA
{
public:
    ClassA() { cout << "ClassA::ClassA()" << endl; }
    virtual ~ClassA() { cout << "ClassA::~ClassA()" << endl; }

    void func1() { cout << "ClassA::func1()" << endl; }
    void func2() { cout << "ClassA::func2()" << endl; }

    virtual void vfunc1() { cout << "ClassA::vfunc1()" << endl; }
    virtual void vfunc2() { cout << "ClassA::vfunc2()" << endl; }
private:
    int aData;
};

class ClassB : public ClassA
{
public:
    ClassB() { cout << "ClassB::ClassB()" << endl; }
    virtual ~ClassB() { cout << "ClassB::~ClassB()" << endl; }

    void func1() { cout << "ClassB::func1()" << endl; }
    virtual void vfunc1() { cout << "ClassB::vfunc1()" << endl; }
private:
    int bData;
};

class ClassC : public ClassB
{
public:
    ClassC() { cout << "ClassC::ClassC()" << endl; }
    virtual ~ClassC() { cout << "ClassC::~ClassC()" << endl; }

    void func2() { cout << "ClassC::func2()" << endl; }
    virtual void vfunc2() { cout << "ClassC::vfunc2()" << endl; }
private:
    int cData;
};

只要基類有虛函數,子類不論實現或沒實現,都有虛函數表。

  • (1) ClassA是基類,有普通函數:func1() func2()。虛函數:vfunc1()vfunc2() ~ClassA()
  • (2) ClassB繼承ClassA, 有普通函數: func1()。虛函數:vfunc1() ~ClassB()
  • (3) ClassC繼承ClassB, 有普通函數: func2()。虛函數:vfunc2() ~ClassC()

基類的虛函數表和子類的虛函數表不是同一個表。虛函數表是在編譯時確定的,屬於類而不屬於某個具體的實例。虛函數在代碼段,僅有一份。

ClassB繼承與ClassA,其虛函數表是在ClassA虛函數表的基礎上有所改動的,變化的僅僅是在子類中重寫的虛函數。如果子類沒有重寫任何父類虛函數,那麼子類的虛函數表和父類的虛函數表在內容上是一致的。

ClassA *a = new ClassB();
a->func1();                    // "ClassA::func1()"   隱藏了ClassB的func1()
a->func2();                    // "ClassA::func2()"
a->vfunc1();                   // "ClassB::vfunc1()"  重寫了ClassA的vfunc1()
a->vfunc2();                   // "ClassA::vfunc2()"

這個結果不難想象,看上圖,ClassA類型的指針a能操作的範圍只能是黑框中的範圍,之所以實現了多態完全是因爲子類的虛函數表指針與虛函數表的內容與基類不同,這個結果已經說明了C++的隱藏、重寫(覆蓋)特性
類ClassC的繼承情況是: ClassC繼承ClassB,ClassB繼承ClassA.這是一個多次單繼承的情況。(多重繼承)

ClassA* a = new ClassC;
a->func1();          // "ClassA::func1()"   隱藏ClassB::func1()               
a->func2();          // "ClassA::func2()"    隱藏ClassC::func2()
a->vfunc1();         // "ClassB::vfunc1()"    ClassB把ClassA::vfunc1()覆蓋了
a->vfunc2();         // "ClassC::vfunc2()"    ClassC把ClassA::vfunc2()覆蓋了

ClassB* b = new ClassC;
b->func1();                // "ClassB::func1()"    有權限操作時,子類優先
b->func2();                // "ClassA::func2()"    隱藏ClassC::func2()
b->vfunc1();            // "ClassB::vfunc1()"    ClassB把ClassA::vfunc1()覆蓋了
b->vfunc2();            // "ClassC::vfunc2()"    ClassC把ClassA::vfunc2()覆蓋了

多重繼承下的虛函數表 (同時繼承多個基類)

多重繼承是指一個類同時繼承了多個基類,假設這些基類都有虛函數,也就是說每個基類都有虛函數表,那麼該子類的邏輯結果和虛函數表是什麼樣子呢?

class ClassA1
{
public:
    ClassA1() { cout << "ClassA1::ClassA1()" << endl; }
    virtual ~ClassA1() { cout << "ClassA1::~ClassA1()" << endl; }

    void func1() { cout << "ClassA1::func1()" << endl; }

    virtual void vfunc1() { cout << "ClassA1::vfunc1()" << endl; }
    virtual void vfunc2() { cout << "ClassA1::vfunc2()" << endl; }
private:
    int a1Data;
};

class ClassA2
{
public:
    ClassA2() { cout << "ClassA2::ClassA2()" << endl; }
    virtual ~ClassA2() { cout << "ClassA2::~ClassA2()" << endl; }

    void func1() { cout << "ClassA2::func1()" << endl; }

    virtual void vfunc1() { cout << "ClassA2::vfunc1()" << endl; }
    virtual void vfunc2() { cout << "ClassA2::vfunc2()" << endl; }
    virtual void vfunc4() { cout << "ClassA2::vfunc4()" << endl; }
private:
    int a2Data;
};

class ClassC : public ClassA1, public ClassA2
{
public:
    ClassC() { cout << "ClassC::ClassC()" << endl; }
    virtual ~ClassC() { cout << "ClassC::~ClassC()" << endl; }

    void func1() { cout << "ClassC::func1()" << endl; }

    virtual void vfunc1() { cout << "ClassC::vfunc1()" << endl; }
    virtual void vfunc2() { cout << "ClassC::vfunc2()" << endl; }
    virtual void vfunc3() { cout << "ClassC::vfunc3()" << endl; }
};

ClassA1是第一個基類,擁有普通函數func1(),虛函數vfunc1() vfunc2()。
ClassA2是第二個基類,擁有普通函數func1(),虛函數vfunc1() vfunc2(),vfunc4()。
ClassC依次繼承ClassA1、ClassA2。普通函數func1(),虛函數vfunc1() vfunc2() vfunc3()。


在多繼承情況下,有多少個基類就有多少個虛函數表指針,前提是基類要有虛函數纔算上這個基類。
如圖,虛函數表指針01指向的虛函數表是以ClassA1的虛函數表爲基礎的,子類的ClassC::vfunc1(),和vfunc2()的函數指針覆蓋了虛函數表01中的虛函數指針01的位置、02位置。當子類有多出來的虛函數時,添加在第一個虛函數表中。
當有多個虛函數表時,虛函數表的結果是0代表沒有下一個虛函數表。" * "號位置在不同操作系統中實現不同,代表有下一個虛函數表。注意:

  • 1.子類虛函數會覆蓋每一個父類的每一個同名虛函數。
  • 2.父類中沒有的虛函數而子類有,填入第一個虛函數表中,且用父類指針是不能調用。
  • 3.父類中有的虛函數而子類沒有,則不覆蓋。僅子類和該父類指針能調用。
ClassA1 *a1 = new ClassC;
a1->func1();               // "ClassA1::func1()"    隱藏子類同名函數
a1->vfunc1();              // "ClassC::vfunc1()"    覆蓋父類ClassA1虛函數
a1->vfunc2();              // "ClassC::vfunc2()"    覆蓋父類ClassA1虛函數
//沒有a1->vfunc3(),父類沒有這個虛函數

ClassA2 *a2 = new ClassC;
a2->func1();               // "ClassA2::func1()"    隱藏子類同名函數
a2->vfunc1();              // "ClassC::vfunc1()"    覆蓋父類ClassA2虛函數
a2->vfunc2();              // "ClassC::vfunc2()"    覆蓋父類ClassA2虛函數
a2->vfunc4();              // "ClassA2::vfunc4()"   未被子類重寫的父類虛函數
//沒有a2->vfunc3(),父類沒有這個虛函數

ClassC *c = new ClassC;
c->func1();                // "ClassC::func1()"
c->vfunc1();               // "ClassC::vfunc1()"
c->vfunc2();               // "ClassC::vfunc2()"
c->vfunc3();               // "ClassC::vfunc3()"
c->vfunc4();               // "ClassA2::vfunc4()"

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章