前面的文章中,我们说到,C++ 对动态多态(动态绑定,函数重载、模板、运算符重载:静态编译期绑定)的支持是通过虚表来实现的,C++ 编译器会为包含 virtual 成员函数的类,在其所占内存空间的前4个字节(刚好是指针的大小)提供一个指向虚表的指针(指向虚表,也即其存放的虚表的地址)。
是否果真如此呢?我们接下来试着验证一下(注:以下做法有违面向对象封装性的要求,属于不安全的访问,除非演示、查看 C++ 内存模型,慎用)。
#include <iostream>
using namespace std;
class Base
{
public:
Base() :_x(0){}
virtual void foo1() { cout << "Base::foo1()" << endl; }
virtual void foo2() { cout << "Base::foo2()" << endl; }
private:
int _x;
};
class Derived :public Base
{
public:
void foo2() { cout << "Derived::foo2()" << endl; }
virtual void foo3() { cout << "Derived::foo3()" << endl; }
private:
int _y;
};
typedef void(*FUNC)();
int main(int, char**)
{
Base b;
long** p = (long**)&b;
//p[0] == > 指向虚表
//p[0][0] == > foo1()
//p[0][1] == > foo2()
// p[1] 表示non-static数据成员
((FUNC)p[0][0])();
// 这里的访问是根据内存模型推算而来,与类成员的访问修饰符无关
((FUNC)p[0][1])();
//p[1] == > 数据成员
cout << "===================" << endl;
Base* pb = new Derived;
p = (long**)pb;
((FUNC)p[0][0])();
// 子类没有重写,调用父类
((FUNC)p[0][1])();
// 子类覆盖,调用子类
((FUNC)p[0][2])();
// 子类所独有的虚函数,调用子类
return 0;
}
继承关系中的两类(Base、Derived)各自的内存模型如下: