从例子入手,考察如下带有虚函数的类的对象内存模型:
class A {
public:
virtual void vfunc1();
virtual void vfunc2();
void func1();
void func2();
virtual ~A();
private:
int m_data1, m_data2;
};
class B : A {
public:
virtual void vfunc1();;
void func2();
virtual ~B();
private:
int m_data3;
};
class C : B {
public:
virtual void vfunc1();
void func();
private:
int m_data1, m_data4;
};
注:在子类中出现与父类相同名称的变量和非虚函数不是最佳实践,这里是为了说明其内存结构。
其对象内存结构见下图。
分析如下:
每个含有虚函数的类在内存中多一根指针(vptr),见图中a,b,c对象中第一个位置,存储的是虚函数表(vtbl)所在的位置
虚函数表(vtbl)存储着所有虚函数的位置,由于其动态绑定特性,在覆写(override)后再子类中存储的虚函数位置与父类不相同
-
分析上述代码,B继承A,所有A中的数据部分也被B继承下来,同时B添加上了自己的数据部分m_data3,加之vptr,组成了B左侧的内存布局
- A中的虚函数vfunc1(),vfunc2()可以被覆写和动态绑定
- 所以在B中,vfunc1()被覆写,其vtbl中对应项指向了新的函数位置(亮蓝色).vfunc2()未被覆写,仍然指向原先位置(深蓝色)
- C与B同理,vfunc1()被覆写,其vtbl中对应项指向了新的函数的位置(橘黄色).vfunc2()未被覆写,仍然指向原先位置(深蓝色)
非虚函数静态绑定,存储在单独的内存空间(code memory section,灰色函数部分),调用时把对象的this指针,传给一个invisible参数,以便确定谁在调用函数
调用虚函数的与的C语言形式如图中下部分所示,其中n表示对应的函数在第几个位置(编译器在建立虚函数表的时候已知),从而实现动态绑定。