C++中虛表是什麼

江水为竭發表於2024-03-18

虛擬函式表,以及虛擬函式指標是實現多型性(Polymorphism)的關鍵機制。多型性允許我們透過基類的指標或引用來呼叫派生類的函式

定義

虛擬函式(Virtual Function)

  • 定義:類中使用virtual 關鍵字修飾的函式 叫做虛擬函式

  • 語法

class Base {
public:
    virtual void show() { cout << "Base show" << endl; }
};

虛擬函式表(Virtual Function Table)

  • 定義:當類含有至少一個虛擬函式時,編譯器會為該類建立一個虛擬函式表。這個表是一個編譯時構建的靜態陣列,儲存了指向類中所有虛擬函式的指標。如果一個派生類重寫了這些函式,那麼在派生類的虛表中,相應函式的指標會被更新為指向派生類中的版本。
  • 作用v-table使得在執行時可以實現函式的動態繫結,允許透過基類的指標或引用呼叫正確的函式版本。

虛擬函式指標(Virtual Pointer)

  • 定義:每個含有虛擬函式的類的物件(例項化出的)會持有一個指向相應虛表的指標,這個指標通常被稱為虛指標(vptr)。vptr是物件執行時的一部分,確保了當透過基類指標呼叫虛擬函式時,能夠查詢到正確的函式實現。
  • 作用:在物件的生命週期開始時,建構函式會設定vptr以指向相應的虛擬函式表。如果有派生類物件,它的建構函式會更新vptr,以指向派生類的虛擬函式表。這保證了透過基類的引用或指標呼叫虛擬函式也會執行最派生類的重寫版本。

示例

#include <iostream>
using namespace std;

class Base {
public:
    virtual void func1() { cout << "Base::func1" << endl; }
    virtual void func2() { cout << "Base::func2" << endl; }
};

class Derived : public Base {
public:
    void func1() override { cout << "Derived::func1" << endl; }
    // func2() 繼承自 Base
};

void printVTable(void* obj) {
    cout << "vptr Address: " << obj << endl;
    void** vTable = *(void***)obj;
    cout << "VTable[0] (func1): " << vTable[0] << endl;
    cout << "VTable[1] (func2): " << vTable[1] << endl;
}

int main() {
    Base* base = new Base();
    Derived* derived = new Derived();

    cout << "Base object:" << endl;
    printVTable(base);

    cout << "\nDerived object:" << endl;
    printVTable(derived);

    delete base;
    delete derived;

    return 0;
}

程式輸出如下,可以看到沒用重寫的func2函式地址是一樣的。

Base object:
vptr Address: 0x8c1510   
VTable[0] (func1): 0x422270
VTable[1] (func2): 0x4222b0

Derived object:
vptr Address: 0x8c1530   
VTable[0] (func1): 0x422330
VTable[1] (func2): 0x4222b0

如下圖所示:

面試題

(來自2024騰訊實習面試)場景題:一個類 A,裡面有一個列印 helloworld 的虛擬函式,然後類 A 會在建構函式里呼叫這個虛擬函式,此時有個類 B,繼承A,重寫了這個 helloworld虛擬函式,問你在建立類 B 時,會列印 A 裡的 helloworld 還是 B 裡的。

程式碼如下:

class A {
public:
    A() {
        print();
    }
    virtual void print() {
        cout << "A print" << endl;
    }
};

class B : public A {
public:
    void print() override {
        cout << "B print" << endl;
    }
};
int main() {
    A* aTemp = new B();
    delete aTemp;
}

解答:基類建構函式執行的時候,派生類的部分尚未初始化,因此呼叫的虛擬函式不會下發到派生類中。

最終會列印 A print,而不是類 B 裡重寫的版本

相關文章