在很多面试或口试题中经常涉及到类,而这其中又不得不说类的继承,虚继承,虚函数等问题,所以涉及到了类的内存布局,其中关于虚拟继承(virtual public)这个话题比较难以懂得,而且不同的编译器环境可能实现的类的内存布局不同,所以本文仅在vs2013编译环境下调试,如果你在像gcc这样的编译器中调试结果会不同(gcc中会把虚基指针和虚函数指针合并)。
首先我们要区分虚函数和虚拟继承:
虚拟继承和虚函数是完全无相关的两个概念。
虚函数实现原理
每个虚函数都会有一个与之对应的虚函数表,该虚函数表的实质是一个指针数组,存放的是每一个对象的虚函数入口地址。对于一个派生类来说,他会继承基类的虚函数表同时增加自己的虚函数入口地址,如果派生类重写了基类的虚函数的话,那么继承过来的虚函数入口地址将被派生类的重写虚函数入口地址替代。那么在程序运行时会发生动态绑定,将基类指针绑定到实例化的对象实现多态。
虚拟继承实现原理
虚继承底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。
对比
1、虚继承对比虚函数的实现原理:他们有相似之处,都利用了虚指针(均占用类的存储空间)和虚表(均不占用类的存储空间)。
2、虚基类依旧存在继承类中,占用存储空间;虚函数不占用存储空间。
3、虚基类表存储的是虚基类相对直接继承类的偏移;而虚函数表存储的是虚函数地址。
测试
准备工作
VS2013使用命令行选项查看对象的内存布局
格式:
[filename].cpp /d1 reportSingleClassLayout[className]
测试代码GitHub地址
测试一:单个虚继承,不带虚函数
#pragma vtordisp(off)
#include <iostream>
using std::cout;
using std::endl;
//测试一:单个虚继承,不带虚函数
class A
{
public:
A() : _ia(10){
}
/*virtual*/
void f()
{
cout << "A::f()" << endl;
}
private:
int _ia;
};
class B
: virtual public A
{
public:
B() : _ib(20){
}
void fb()
{
cout << "B::fb()" << endl;
}
/*virtual*/
void f()
{
cout << "B::f()" << endl;
}
/*virtual*/
void fb2()
{
cout << "B::fb2()" << endl;
}
private:
int _ib;
};
int main(void)
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
B b;
system("pause");
return 0