一、什么是菱形继承
是一种多重继承的特殊情况,一图即懂 :

代码表达:
class A {};
class B : public A {};
class C : public A {};
class D : public B ,public C{};
其中 A 作为整个菱形继承 的 最先被继承的类 ,称之为 虚基类
二、菱形继承二义性问题
1.数据冗余
当 最下层 派生类 (D) 生成实例对象的 时候 ,其内部 空间 中 包含B 的对象空间,也包含C的对象空间 ,若想通过 B 或 C 访问 A 的 成员,就会引发访问不明确的问题(数据冗余所导致的),因为 B 中有一份A 的数据,C 中也有一份 A 的数据 。
代码解释:
class A { public: int _a = 10; };
class B : public A {};
class C : public A {};
class D : public B, public C {};
int main()
{
D d;
d._a; // 此处编译不通过 报访问不明确
return 0;
}

2.解决方案
使用virtual 关键字 实现 虚继承类 即 B 和 C 进行虚继承 A , 效果是 B C 共同 维护同一个 A 空间
代码解释:
class A { public: int _a = 10; };
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};
int main()
{
D d;
d._a; // 此时便可以编译通过
return 0;
}
此时 d 访问的 _a 数据 全局只有一份 ,便可以通过编译。
三、深入了解底层解决原理(虚基表的应用)
1.虚基表
当B C 分别虚继承类 A 时 ,底层是如何 维护 一个公共空间 A 呢,答案就是 通过虚基表。
(1) 生成时机
在编译期,如果一个类 有 virtual 修饰的 虚继承类,则 会 生成一个对应的虚基表。
(2) 存储区域
生成的虚基表作为后面 访问 公共空间的依据,存放在 静态区。
(3) 内容
B C 是 A 的派生类,所以存储的是B C 对应空间地址到 A 空间地址的距离
(4) 作用流程分析与验证
首先要了解 d 内部的空间占用情况:
当 最底层派生类 D d 对象实例化,其内部空间 会包含三部分 : (c++中是先继承的先构造)
按地址由高到低
B 空间: 会 先包含一个 vbptr 指针 用于 访问虚基表,+ B空间的内容
C 空间:同样也会包含一个vbptr 指针 也用于访问虚基表,+ C 空间的内容
D 自身的数据
当访问 A 中空间数据时,首先会先 在 B C 空间中随机选择一个 作为入口(这里假设选B),
先得到 B中的 vbptr 然后访问对应 虚基表 ,然后通过虚基表找到 B 空间 和 A 空间的 距离(地址偏移量),然后 同一个 B 空间地址 + 对应偏移量 = A 空间地址 ,进而找到对应的数据
实例:
class A { public: int _a = 4; };
class B : public virtual A { int _b = 1; };
class C : public virtual A { int _c = 2; };
class D : public B, public C { int _d = 3; };
int main()
{
D d;
d._a;
return 0;
}
内存调试图解:

四、菱形继承与虚基表两句话总结
菱形继承用 virtual 虚继承把顶层虚基类变成“唯一共享实例”,底层派生类只面对一块内存,消除歧义。
底层对象通过中层类对象空间里的 vbptr 查全局“虚基表”获得偏移,算出共享基类地址再访问。

7240

被折叠的 条评论
为什么被折叠?



