什么是菱形继承?
1、菱形继承是多继承关系的一种特殊情况
2、继承关系如下图:
(菱形继承关系图) (基于菱形继承关系的对象模型)
3、菱形继承代码:
class B
{
public:
int _b;
};
class C1 : public B
{
public:
int _c1;
};
class C2 : public B
{
public:
int _c2;
};
class D : public C1, public C2
{
public:
int _d;
};
分析这段代码,结合上图可以看到:
C1、C2继承B,且继承权限为public; D继承C1、C2,且继承方式为public。
对这段代码进行测试:
int main()
{
cout << sizeof(D) << endl;
D d;
//d._b = 1;
d.C1::_b = 1;
d._c1 = 2;
d.C2::_b = 3;
d._c2 = 4;
d._d = 5;
return 0;
}
结合菱形继承关系的对象模型,可得派生类D大小为20。和测试结果相符合。
但是有一个问题:
当我们创建一个D类对象d,再通过d给继承自基类的成员_b赋值时,这里就会报错。
原因:
_b在派生类D中有两份,一份继承自C1,另一份则来自C2。当我们给_b赋值时,编译器不知道该给哪个_b赋值,则会报错。
这也是菱形继承的一个缺陷:造成数据冗余和二义性问题。
解决办法:
1、在给存在双份的_b赋值时,前面加上作用域,指出是来自哪个类的_b,则可解决这个问题。例:d. C1::_b = 1;
2、C++为了解决菱形继承的数据冗余和二义性问题,专门给出了一种解决办法,就是菱形虚拟继承。
下面我们 来了解一下菱形虚拟继承:
什么是菱形虚拟继承?
1、专门为解决菱形继承带来的数据冗余和二义性问题而给出的一种继承方式。这种继承方式在除菱形继承之外,通常不会使用。
2、菱形虚拟继承关系如下图:
(菱形虚拟继承关系图)
3、菱形虚拟继承代码:
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;
};
分析上面这段代码,对比前面菱形继承代码比较:
不同之处:仅仅是在C1、C2继承B时继承权限前面加上了关键字virtual。
然后对这段代码进行测试:
cout << sizeof(D) << endl;
D d;
d._b = 1;
d._c1 = 2;
d._c2 = 3;
d._d = 4;
发现:
1、之前非虚拟继承时存在的数据冗余和二义性问题已经得到了解决,编译器不再报错。
于是我们猜想菱形虚拟继承的数据模型应该是这样的:
在派生类D中只有一份_b。所以对应的D类对象大小应该减小为16。
但是:
2、派生类D的大小不但没有减小,反而增大为24。
这是怎么回事呢?
这里我就直接给出答案了:
原因:
菱形虚拟继承为了解决菱形继承所产生的数据冗余和二义性问题,_b 在D中只保留的一份,但是在C1 ,和C2中分别添加了一个指针,目的是为了寻找_b。
这两个指针都指向一张表,表里存放的是该指针到_b的偏移量,这两个指针叫虚基表指针,这张表叫偏移量表格,也叫虚基表。
通过偏移量可以找到最下面的_b。
图解:
(菱形虚拟继承数据模型) (虚基表数据模型)
总结:
1、菱形虚拟继承会产生数据冗余和二义性问题。
2、为解决数据冗余和二义性问题,C++给出菱形虚拟继承专门解决这个问题。
3、在开发时,不建议设计出多继承,一定不要设计出菱形继承,否则底层实现可能会很复杂,代码性能可能也不是很好。