C++原理剖析之虛函數表

最近在看C++的一些相關的機制,再加上剛看了陳皓大神的早期關於虛函數表的博客,便自己動手通過編程瞭解了下虛函數表的原理。

前言

c++是通過虛函數來實現多態的的機制。我們可以通過將父類的指針指向子類的實例,如Base b = new Derive(),如此一來,如果子類Derive中重載了父類中的一個函數h(),那麼調用b->h()等同於調用Derive dd->h()。我們這裏來剖析下虛函數表的實現機制。

1 虛函數表的內部結構

我們可以通過編程來對虛函數表的內部結構來一窺究竟。如下代碼:

#include <iostream>
using namespace std;
class Base{
public:
    virtual void f() { cout << "Base::f" << endl; }
    virtual void g() { cout << "Base::g" << endl; }
    virtual void h() { cout << "Base::h" << endl; }
};

我們構造了一個基類,定義了三個虛函數f(),g(),h(),分別輸出對應的描述。我們通過以下代碼來得到該基類的虛函數表地址,和虛函數表中各個函數的地址。

int main() {
    Base1 b;
    cout << "虛函數表地址:" << (long*)(&b)+1 << endl;
    cout << "虛函數表-第一個函數地址:" << ((long*)*(long*)(&b)) << endl;
    cout << "虛函數表-第二個函數地址:" << ((long*)*(long*)(&b)+1) << endl;
    cout << "虛函數表-第三個函數地址:" << ((long*)*(long*)(&b)+2) << endl;
}

我的機器是64位系統,其中函數指針大小爲16位,因此使用long*強轉。運行結果如下:

虛函數表地址:0x7fffb023d7a8
虛函數表-第一個函數地址:0x466c88
虛函數表-第二個函數地址:0x466c90
虛函數表-第三個函數地址:0x466c98

爲了進一步驗證這些函數地址的有效性,使用一個函數指針pFun來指向這些函數:

int main() {
    typedef void(*Fun)(void);
    Base1 b;
    Fun pFun = NULL;
    pFun = (Fun)*((long*)*(long*)(&b));
    pFun();
    pFun = (Fun)*((long*)*(long*)(&b)+1);
    pFun();
    pFun = (Fun)*((long*)*(long*)(&b)+2);
    pFun();
}

結果如下:

Base::f
Base::g
Base::h

由此我們可知該基類的虛函數表的結構應該是這樣:
在這裏插入圖片描述

2 一般繼承無函數重寫的情況

我們來分析單繼承有子類的情況,而且該子類有自己的虛函數,並無對父類的函數進行重寫。數據結構如下:

class Base{
public:
    virtual void f() { cout << "Base::f" << endl; }
    virtual void g() { cout << "Base::g" << endl; }
    virtual void h() { cout << "Base::h" << endl; }
};
class Derive{
public:
    virtual void f1() { cout << "Derive::f1" << endl; }
    virtual void g1() { cout << "Derive::g1" << endl; }
    virtual void h1() { cout << "Derive::h1" << endl; }
};

通過編程驗證可知,其父類的虛函數表沒有變化,其子類的虛函數表是這樣:
在這裏插入圖片描述

3 一般繼承有函數重寫的情況

如果其子類有函數重寫的情況,如下數據結構:

class Base{
public:
    virtual void f() { cout << "Base::f" << endl; }
    virtual void g() { cout << "Base::g" << endl; }
    virtual void h() { cout << "Base::h" << endl; }
};
class Derive{
public:
    void f() { cout << "Derive::f" << endl; }
    virtual void g1() { cout << "Derive::g1" << endl; }
    virtual void h1() { cout << "Derive::h1" << endl; }
};

可以看出子類重寫了父類的 f 函數,這時候再看子類的虛函數表結構:
在這裏插入圖片描述可以看出子類的f函數覆蓋了父類的f函數的位置,其他位置不變。
不妨編程驗證一下:

int main() {
    typedef void(*Fun)(void);
    Base b;
    Derive d;
    Fun pFun = NULL;
    pFun = (Fun)*(long*)*((long*)(&d));
    pFun();
}

結果:

Derive::f

4 多重繼承無函數重寫的情況


待續,,,

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