1.基礎(chǔ)理論
為了實(shí)現(xiàn)虛函數(shù)遗菠,C ++使用一種稱(chēng)為虛擬表的特殊形式的后期綁定。該虛擬表是用于解決在動(dòng)態(tài)/后期綁定方式的函數(shù)調(diào)用函數(shù)的查找表维费。虛擬表有時(shí)會(huì)使用其他名稱(chēng)种蝶,例如“vtable”,“虛函數(shù)表”靡羡,“虛方法表”或“調(diào)度表”系洛。
虛擬表實(shí)際上非常簡(jiǎn)單,雖然用文字描述有點(diǎn)復(fù)雜略步。首先描扯,每個(gè)使用虛函數(shù)的類(lèi)(或者從使用虛函數(shù)的類(lèi)派生)都有自己的虛擬表。該表只是編譯器在編譯時(shí)設(shè)置的靜態(tài)數(shù)組趟薄。虛擬表包含可由類(lèi)的對(duì)象調(diào)用的每個(gè)虛函數(shù)的一個(gè)條目绽诚。此表中的每個(gè)條目只是一個(gè)函數(shù)指針,指向該類(lèi)可訪問(wèn)的派生函數(shù)。
其次恩够,編譯器還會(huì)添加一個(gè)隱藏指向基類(lèi)的指針卒落,我們稱(chēng)之為vptr。vptr在創(chuàng)建類(lèi)實(shí)例時(shí)自動(dòng)設(shè)置蜂桶,以便指向該類(lèi)的虛擬表儡毕。與this指針不同,this指針實(shí)際上是編譯器用來(lái)解析自引用的函數(shù)參數(shù)扑媚,vptr是一個(gè)真正的指針腰湾。
因此,它使每個(gè)類(lèi)對(duì)象的分配大一個(gè)指針的大小疆股。這也意味著vptr由派生類(lèi)繼承费坊,這很重要。
2.調(diào)用圖
3. 代碼展示:
#include <iostream>
#include <stdio.h>
using namespace std;
/**
*@ 函數(shù)指針
*/
typedef void(*Fun)();
/**
@基類(lèi)
*/
class Base {
public:
Base() {};
virtual void fun1()
{
cout << "Base::fun1()" << endl;
}
virtual void fun2()
{
cout << "Base::fun2()" << endl;
}
virtual void fun3() {}
~Base() {};
};
/**
*@brief 派生類(lèi)
*/
class Derived:public Base
{
public:
Derived() {};
void fun1()
{
cout << "Derived::fun1()" << endl;
}
void fun2()
{
cout << "DerivedClass::fun2()" << endl;
}
~Derived(){}
};
/**
* @brief 獲取vptr地址與func地址,vptr指向的是一塊內(nèi)存旬痹,這塊內(nèi)存存放的是虛函數(shù)地址葵萎,
這塊內(nèi)存就是我們所說(shuō)的虛表
*
* @param obj
* @param offset
* @return
*/
Fun getAddr(void *obj, unsigned int offset)
{
cout << "================" << endl;
void *vptr_addr = (void*)*(unsigned long*)obj; //64位操作系統(tǒng)占8字節(jié),通過(guò)*(unsigned long *)obj取出前8字節(jié)唱凯,
//即vptr指針
printf("vptr_addr: %p\n", vptr_addr);
/**
* @brief 通過(guò)vptr指針訪問(wèn)virtual table羡忘,因?yàn)樘摫碇忻總€(gè)元素(虛函數(shù)指針)在64位編譯器下是8個(gè)字節(jié),
因此通過(guò)*(unsigned long *)vptr_addr取出前8字節(jié)磕昼,后面加上偏移量就是每個(gè)函數(shù)的地址卷雕!
*/
void *func_addr = (void*)*((unsigned long*)vptr_addr + offset);
printf("func_addr: %p\n", func_addr);
return (Fun)func_addr;
}
int main(void)
{
Base ptr;
Derived d;
Base *pt = new Derived(); //基類(lèi)指向派生類(lèi)實(shí)例
Base &pp = ptr; // 基類(lèi)引用指向基類(lèi)實(shí)例
Base &p = d; //基類(lèi)引用指向派生類(lèi)實(shí)例
cout << "基類(lèi)對(duì)象直接調(diào)用" << endl;
ptr.fun1();
cout << "派生類(lèi)對(duì)象調(diào)用派生類(lèi)實(shí)例" << endl;
d.fun1();
cout << "基類(lèi)引用調(diào)用基類(lèi)實(shí)例" << endl;
pp.fun1();
cout << "基類(lèi)指針指向派生類(lèi)實(shí)例并調(diào)用派生類(lèi)虛函數(shù)" << endl;
pt->fun1();
cout << "基類(lèi)引用指向派生類(lèi)實(shí)例并調(diào)用派生類(lèi)虛函數(shù)" << endl;
p.fun1();
//手動(dòng)查找vptr和vtable
Fun f1 = getAddr(pt, 0);
(*f1)();
Fun f2 = getAddr(pt, 1);
(*f2)();
delete pt;
return 0;
}
4. 結(jié)果展示與分析
其過(guò)程為:首先程序識(shí)別出fun1()是個(gè)虛函數(shù),其次程序使用pt->vptr來(lái)獲取Derived的虛擬表票从。第三漫雕,它查找Derived虛擬表中調(diào)用哪個(gè)版本的fun1()。這里就可以發(fā)現(xiàn)調(diào)用的是Derived::fun1()峰鄙。因此pt->fun1()被解析為Derived::fun1()!