上一篇我们已经讨论了继承体系下派生类的对象模型https://blog.csdn.net/a15929748502/article/details/80893870
这一篇我们来解决上一篇中提出的问题:在菱形继承里,B的成员变量在D中存了两份,这显然是不合理的,不仅浪费了空间,还产生了二义性,如何避免这种事情的发生呢,这就是接下来要讨论的问题
这里就不得不提到今天的主题虚拟继承了
虚拟继承的作用;针对上片的问题具体说虚拟继承可以在上述情况中起到节省空间的作用,同时也解决了二义性问题,它会让B成为一个虚拟基类被C1和C2继承,这时D再继承C1,C2,这时D就只会保存一份B中的成员了。
也就是说他可以解决从不同途径继承来的同一基类,会在子类中存在多份拷贝,浪费空间,并且产生二义性的问题
虚拟继承的格式:
class 子类名称:virtual 继承权限 父类名称
{
。。。。。。
};
它是通过什么方式解决呢,我们通过学习可以知道一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。其中虚基类指针就指向虚基类表。
而虚表中就记录着直接继承类相对于自己的偏移量,和虚基类相对直接继承类的偏移量。
我们可以用代码来验证一下
#include <iostream>
using namespace std;
class B
{
public:
int _b;
};
class C1:virtual public B
{
public:
int _c1;
};
class C2 :virtual public B
{
public:
int _c2;
};
class D : public C1, public C2
{
public:
int _d;
};
int main()
{
D d;
d._b=1;
d._c1=2;
d._c2=3;
d._d=4;
cout<<*(int*)&d<<endl;
cout<<*((int*)&d+1)<<endl;
cout<<*((int*)&d+2)<<endl;
cout<<*((int*)&d+3)<<endl;
cout<<*((int*)&d+4)<<endl;
cout<<*((int*)&d+5)<<endl;
cout << sizeof(B) << endl; //4
cout << sizeof(C1) << endl; //12
cout << sizeof(C2) << endl; //12
cout << sizeof(D) << endl; //24
return 0;
}
运行一下
[root@localhost C++]# ./a.out
134515356
2
134515368
3
4
1
4
12
12
24
可见我们的猜想是正确的
我们可以画出对象模型
这就是虚拟继承的对象模型
可以看出虚基类在对象模型的最下面。
这样的模型有什么好处呢,于普通继承有有什么差别
1,从访问方面
普通继承直接访问
虚拟继承通过虚基类表中的偏移量来访问(这是解决问题的关键)
2,构造函数
普通继承没有系统合成的构造函数
虚拟继承有系统合成的构造函数将偏移量表格地址放对象前四个字节,多一个参数检测是否为虚拟继承。
3,对象模型
普通继承基类在最上,继承类在下。
虚拟继承(虚)基类在最下,而派生类在上。
如图