一、多继承
多继承:一个子类有多个父类称为多继承。
多继承存在问题导致二义性:派生类访问基类成员出现的不确定性。
class B1
{
public:
void SetValue()
{
cout << "B1::SetValue" << endl;
}
int _b1;
int _c;
};
class B2
{
public:
void SetValue()
{
cout << "B2::SetValue" << endl;
}
int _b2;
int _c;
};
class D :public B1, public B2
{
public:
int _d;
};
int main()
{
D d;
//d.SetValue();
//d._c = 10;//因为D继承了B1和B2,并且两个基类中都包含_c成员,所以访问不确定。同理函数也是一样
//解决上述问题,需要显示调用
d.B1::SetValue();
d.B2::_c = 10;
return 0;
}
多继承对象模型特点:基类在上,派生类在下,谁先继承谁在上。
二、虚拟继承
在多继承中有一种情况如下图继承关系:菱形继承
菱形继承缺点导致在D中访问B成员造成的数据二义性,和对B中数据存储两份导致数据冗余问题。
解决菱形继承首先我们了解虚拟继承。
虚拟继承:在继承的同时添加virtual关键字
至此,已经知道了虚拟继承对象模型布局,那么派生类部分的前四个字节到底是什么呢?
扫描二维码关注公众号,回复:
5444307 查看本文章
派生类对象前四个字节就是偏移量表格的地址(我们称该地址为虚基表指针,偏移量表格称为虚基表)
单继承和虚拟继承对象模型区别:
- 对象模型中派生类和基类位置相反。单继承:基类在上,派生类在下。虚拟继承:基类在下,派生类在上。
- 虚拟继承多了四个字节,用于存储虚基表的地址。存储在派生类部分的前四个字节。并且虚基表指针实在创建派生类对象时从构造函数填入。
- 对基类成员访问方式不同。普通单继承是直接访问,虚拟继承需要经过三步才可以访问。
- 虚拟继承如果用户没有显示给出构造函数,编译器会默认生成一个派生类构造函数。而普通单继承如果没有必要,编译器也不会生成。
虚拟继承中默认生成的构造函数作用?
构造函数通过参数控制将偏移量表格(虚基表指针)填入到虚拟继承派生类对象模型前四个字节中。该参数控制:如果是虚拟继承,那么会push 1;否则,不是虚拟继承。所以可见 1 是虚拟继承的标志。
三、菱形虚拟继承
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 = 5;
return 0;
}
从上述对象模型可知,菱形虚拟继承解决了菱形继承的二义性和数据冗余。