通过菱形继承剖析虚继承解决二义性和数据冗余问题
2017-11-05 17:39
369 查看
什么是菱形继承?
假设有一个类A,它有两个子类,分别为类B和类C,再有一个类D又继承了B类和C类;如图:
类似于这种,有子类对象包含多份父类对象的继承模型称为菱形继承。
上述菱形继承体系中,类D多重继承了类B和类C,因此,类D含有两份基类A的成员;此种继承方式会造成两个问题:二义性和数据冗余。
先看一段代码:
可以看到:编译器无法确定_a是属于类B的还是类C的,这就是二义性问题。那如何解决这个问题?
可以通过添加域的访问限定符解决:
看一下是否赋值成功:
从监视窗口可看到,对类B和类C的_a都成功赋值。
但是还有另一个问题没有解决:数据的冗余。
因此,引入虚拟继承机制;虚继承可以同时解决菱形继承的二义性和数据冗余的问题。
我们需要在类B和类C继承基类A时加入virtual,这样保证了在子对象创建时,只保存了基类A的一份拷贝。
(C++使用虚拟继承,解决了从不同路径继承来的相同基类的数据成员在内存中有不同的拷贝造成数据不一致的问题,将共同基类设置为虚基类,这时从不同路径继承的虚基类在内存就只有一个映射。)
可以发现_a被赋值为20,并没有10的出现,说明在类D的对象模型中只存在一个_a。
在探索菱形虚拟继承的实现原理前,我们分别看一下派生类D的大小和菱形虚拟继承派生类D的大小:
菱形继承派生类D的大小:
菱形虚拟继承派生类D的大小:
可以看到两者的区别:菱形虚拟继承比菱形继承多了四个字节的大小。
我们可以通过内存看一下菱形虚拟继承中派生类D的对象在内存中的情况:
通过内存可以看出基类A的成员变量_a在类D中只有一份,因此也已经解决了数据冗余的问题;但是,内存中蓝色框中的地址存的是什么呢?
我们称它为虚基表地址。可以看下这个地址存放的是什么:
总图:
下面我们可以看一下菱形继承的对象模型和菱形虚拟继承对象模型:
菱形继承对象模型:
菱形虚拟继承对象模型:
总结:
虚拟继承虽然解决了菱形继承的产生的二义性和数据冗余问题,但是如同上述的例子为了解决一个int数据冗余,却开辟了两个存放虚基表的空间;而且访问虚基类数据时,要通过虚基表进行间接访问,效率就会比较低,带来了性能上的损耗,所以非必要的时候尽量避免菱形继承,尝试换一种设计模式(但是当数据冗余的程度很大时,使用虚拟继承会更)。
假设有一个类A,它有两个子类,分别为类B和类C,再有一个类D又继承了B类和C类;如图:
类似于这种,有子类对象包含多份父类对象的继承模型称为菱形继承。
上述菱形继承体系中,类D多重继承了类B和类C,因此,类D含有两份基类A的成员;此种继承方式会造成两个问题:二义性和数据冗余。
先看一段代码:
#include<iostream> using namespace std; class A //基类A { public: int _a; }; class B:public A //子类B { public: int _b; }; class C:public A //子类C { public: int _c; }; class D :public B, public C //多重继承B和C的子类D { public: int _d; }; void test() { D d; d._a = 10; } int main() { test(); system("pause"); return 0; }看一下编译结果:
可以看到:编译器无法确定_a是属于类B的还是类C的,这就是二义性问题。那如何解决这个问题?
可以通过添加域的访问限定符解决:
void test() { D d; d.B::_a = 10; d.C::_a = 20; d._b = 30; d._c = 40; d._d = 50; }
看一下是否赋值成功:
从监视窗口可看到,对类B和类C的_a都成功赋值。
但是还有另一个问题没有解决:数据的冗余。
因此,引入虚拟继承机制;虚继承可以同时解决菱形继承的二义性和数据冗余的问题。
我们需要在类B和类C继承基类A时加入virtual,这样保证了在子对象创建时,只保存了基类A的一份拷贝。
(C++使用虚拟继承,解决了从不同路径继承来的相同基类的数据成员在内存中有不同的拷贝造成数据不一致的问题,将共同基类设置为虚基类,这时从不同路径继承的虚基类在内存就只有一个映射。)
#include<iostream> using namespace std; class A { public: int _a; }; class B:virtual public A { public: int _b; }; class C :virtual public A { public: int _c; }; class D :public B, public C { public: int _d; }; void test() { D d; d._a = 10; d._a = 20; d._b = 30; d._c = 40; d._d = 50; } int main() { test(); system("pause"); return 0; }看一下监视窗口:
可以发现_a被赋值为20,并没有10的出现,说明在类D的对象模型中只存在一个_a。
在探索菱形虚拟继承的实现原理前,我们分别看一下派生类D的大小和菱形虚拟继承派生类D的大小:
菱形继承派生类D的大小:
菱形虚拟继承派生类D的大小:
可以看到两者的区别:菱形虚拟继承比菱形继承多了四个字节的大小。
我们可以通过内存看一下菱形虚拟继承中派生类D的对象在内存中的情况:
通过内存可以看出基类A的成员变量_a在类D中只有一份,因此也已经解决了数据冗余的问题;但是,内存中蓝色框中的地址存的是什么呢?
我们称它为虚基表地址。可以看下这个地址存放的是什么:
总图:
下面我们可以看一下菱形继承的对象模型和菱形虚拟继承对象模型:
菱形继承对象模型:
菱形虚拟继承对象模型:
总结:
虚拟继承虽然解决了菱形继承的产生的二义性和数据冗余问题,但是如同上述的例子为了解决一个int数据冗余,却开辟了两个存放虚基表的空间;而且访问虚基类数据时,要通过虚基表进行间接访问,效率就会比较低,带来了性能上的损耗,所以非必要的时候尽量避免菱形继承,尝试换一种设计模式(但是当数据冗余的程度很大时,使用虚拟继承会更)。
相关文章推荐
- 1:分析菱形继承的问题。 2:剖析虚继承是怎么解决二义性和数据冗余的。
- 菱形继承问题和虚继承是如何解决二义性与数据冗余的
- 剖析C++是如何解决菱形继承的二义性和数据冗余的
- 通过代码解决全角问题Form继承法
- 菱形继承问题分析及其在C++的解决方法(虚继承)
- C++学习笔记(9)——使用范围运算符解决继承中的二义性问题
- 菱形继承产生的问题及解决
- 多重继承引发的二义性问题及解决方法分析
- c++菱形继承产生的问题及解决
- 菱形继承,二义性和数据冗余
- 【编程语言】如何解决菱形继承问题
- 什么是菱形继承带来的二义性问题
- 解决 通过继承QAbstractTableModel并实现headerData方法给QTableView添加标题时,标题不显示 的问题
- 菱形继承二义性解决方法--虚拟继承
- VS2012 opencv 无法删除“继承的值”问题解决方案
- Android使用wifi通过UDP协议发送广播数据包给PC接收不到问题解决方法
- 通过修改manifest文件来解决Vista/Win7/Win8/win10下应用程序兼容性问题
- 解决 https 证书验证不通过的问题
- 通过重建图标缓存文件来解决程序图标显示错误的问题
- iOS-77-解决iOS9闪退,在iOS10上正常的问题;以及解决百度导航sdk导致审核不通过