虛函數表和虛函數指針

  • 虛函數的地址存放於虛函數表中。運行期多態就是通過虛函數和虛函數表實現的。
    類的對象內部會有指向類內部的虛表地址的指針(每個類用了一個虛表,每個類的對象用了一個虛指針)。
    通過這個指針調用虛函數。虛函數的調用會被編譯器轉換爲對虛函數表的訪問。

  • ptr->f(); //ptr代表this指針,f是虛函數
    *(ptr->vptr[1])(ptr);
    ptr代表一個this指針,ptr指向的vptr是類內部的虛表指針。這個虛表指針會被放在類的最前方,
    1就是虛函數指針在虛函數表中的索引值。在這個索引值表示的虛表的槽中存放的就是f()的地址。

  • 虛表指針的名字也會被編譯器更改,所以在多繼承的情況下,類的內部可能存在多個虛表指針。通過不同的
    名字被編譯器標識。

  • 虛函數表中可能還存在其他的內容,如用於RTTI的type_info類型。或者直接將虛基類的指針放在虛表中。
    壓制多態可以通過域操作符進行

  • 單繼承
    這種情況下,派生類中僅有一個虛函數表。這個虛函數表和基類的虛函數表不是一個表(無論派生類有沒有
    重寫基類的虛函數),但是如果派生類沒有重寫基類的虛函數的話,基類和派生類的虛函數表指向的函數地址都是
    相同的。

例子

class A
{
public:
	A(int _a1 = 1) : a(_a1) { }
	virtual void f() { cout << "A1::f" << endl; }
	virtual void g() { cout << "A1::g" << endl; }
	~A() {}
private:
	int a;
};
class B : public A
{
public:
	B(int _a1 = 1, int _c = 4) :A(_a1), b(_c) { }
	virtual void g() { cout << "C::g" << endl; }
private:
	int b;
};

*因爲A有virtual void f(),和g(),所以編譯器爲A類準備了一個虛表vtableA,內容如下:
A::f的地址
A::g的地址
B因爲繼承了A,所以編譯器也爲B準備了一個虛表VtableB,內容如下:
A::f的地址
B::g的地址
因爲B::g重寫了,所以B的虛表的g放的是B::g的入口地址,但是f是從上面的A繼承下來的,所以
f的地址是A::f的入口地址。
在構造B的對象的時候即(B b),編譯器分配內存空間,除了classA的int a,B的成員int b;以外,還分配了一個虛指針vptr,
指向B的虛表vtableB,b的佈局如下:
vptr:指向B的虛表vtableB
int a:繼承A的成員
int b:B成員
A pa =&b;
pa的結構就是A的佈局(就是說用pa只能訪問到b對象的前兩項,訪問不到第三項int b)
pa->g()中,編譯器知道的是,g是一個聲明爲virtual的成員函數,而且其入口地址放在表格的第2項,那麼編譯器
編譯這條語句的時候就如是轉換(pa->vptr)[1]。這一項放的是B::g()的入口地址,則就實現了多態

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