前言
C++中的虚函数的作用主要是实现了多态的机制。基类定义虚函数,子类可以重写该函数。
实现机制
每个类对象添加一个成员,该成员中保存了一个指向函数地址数组的指针,称为虚表指针(vptr),该数组称为虚函数表(virtual function table, vtbl),即,每个类使用一个虚函数表,每个类对象用一个虚表指针。
实例分析
测试代码及输入结果
#include <iostream>
using namespace std;
class Base
{
public:
virtual void a() { cout << "Base fun a" << endl; }
virtual void b() { cout << "Base fun b" << endl; }
virtual void c() { cout << "Base fun c" << endl; }
private:
int x;
};
class Child : public Base
{
virtual void b() { cout << "Child fun b" << endl; }
};
int main()
{
typedef void(*Fun)(void);
Child c;
int x = sizeof(c);/* x=8 (x86) */
int* pBase = (int*)(&c);
cout << "对象地址:" << pBase << endl;
int* pFun = (int*)(*pBase);
cout << "虚函数表地址:" << pFun << endl;
Fun pFun1 = (Fun)*(pFun + 0);
pFun1();
Fun pFun2 = (Fun)*(pFun + 1);
pFun2();
Fun pFun3 = (Fun)*(pFun + 2);
pFun3();
return 0;
}
说明:定义了基类Base,包含三个虚函数a、b、c和一个int成员变量x,子类Child重新实现了函数b。
在vs2015 x86下编译运行输出如下:
对象地址:0036F7BC
虚函数表地址:011A9C80
Base fun a
Child fun b
Base fun c
结果分析
- sizeof(C)结果为8,是因为在32位处理器上,虚标指针占用4字节,成员变量x占用4字节。
- 查看对象地址0x0036F7BC,其内存前四个字节正好是虚函数表的地址0x011A9C80(由于inter处理器是小端模式,所以字节序看起来刚好相反)。
- 查看虚函数表地址处的内存0x011A9C80,可以看到对象c包含的虚函数的具体地址,其中第一个和第三个为基类Base的a、c函数地址,第二个为子类Child的b函数地址。
- 对象c在内存中函数地址分布示意图如下图所示:
虚函数表指针保存在对象起始地址的前4字节(x64为8字节),其后紧跟的为成员变量。注意,前4字节是只存储了对象的虚函数表的地址,虚函数表存储在该地址指向的地址区。