上一次我们学习了继承相关的知识, 这次我们来看看继承的问题 — 菱形继承.
就拿上面的经典菱形继承来说, 从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在D类的对象中A类成员会有两份。
.
下面给出菱形继承的代码 :
//菱形继承
class AA {
public:
int _aa;
};
class BB : public AA {
//AA::_aa
public:
int _bb;
};
class CC : public AA {
//AA::_aa
public:
int _cc;
};
class DD : public BB, public CC {
//BB::_a CC::_a
public:
int _dd;
};
我们可以通过调试看一下DD类对象的数据模型
内存按照继承顺序排列 : 先存放继承的父类(按顺序), 然后存放自己的成员
d.BB::_aa = 1;
d._bb = 2;
d.CC::_aa = 5;
d._cc = 3;
d._dd = 4;
菱形继承: 公共的基类成员会存在两份, 数据冗余, 且使用时要加作用域, 比较麻烦
通过显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
下面是d对象的内存窗口, 一行显示4字节, 可以看到我写的成员变量顺序就是他在内存中排布的顺序
虚拟继承解决数据冗余和二义性的原理
拿上面的例子来说, 定义方式是B, C继承A的时候, 在继承方式前面加上virtual关键字
代码如下 :
//菱形继承
class AA {
public:
int _aa;
};
class BB : virtual public AA {
public:
int _bb;
};
class CC : virtual public AA {
public:
int _cc;
};
class DD : public BB, public CC {
public:
int _dd;
};
为了研究虚拟继承如何解决数据冗余的问题, 我们来看看他的内存分布
下面是虚拟继承下的d对象内存分布情况 :
我们可以看到, 本来BB::_aa 和 CC::_aa 位置的数据变了, 这个数据没有什么规律, 这其实是两个指针, 叫做虚基表指针, 指向两个虚基表, 虚基表中存放着偏移量, 可以通过这个偏移量找到数据_aa
我的机器是小端字节序, 低地址存数据的低位
那么这两个地址转换过来就是 0x00feec28 和 0x00feed00
通过内存窗口的变化, 我们可以看到, 现在数据_aa存在下面的标红地址处
这么一看, 上面的偏移量 + 地址 正好就是FD74这个内存地址
这样就解决了数据冗余的问题, 使得_aa只存在一份, 下面我们运行代码看一波
d.BB::_aa = 1;
d._bb = 2;
d.CC::_aa = 5;
d._cc = 3;
d._dd = 4;
d._aa = 8;
可以看到, 用了虚拟继承之后, BB::_aa 和CC::_aa, 与直接用_aa改变的是同一块内存空间, 说明_aa只有一份
OK, 菱形继承的问题就说到这