当前位置:   article > 正文

菱形虚拟继承对象内存模型详解_对象模型的菱形是什么

对象模型的菱形是什么

目录

一、菱形继承概念及其缺点

二、解决菱形继承问题

三、对象模型探究

1、存在菱形继承问题的对象模型

2、菱形虚拟继承的对象模型

3、B类对象模型

四、继承与组合

五、总结


一、菱形继承概念及其缺点

单继承: 一个子类只有一个直接父类时称这个继承关系为单继承

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

因为有多继承的存在,导致出现菱形继承问题。C++语言作为编程语言老大哥(也是第一个吃螃蟹的)无法避免的的踩中了这个大坑。后来的一些面向对象编程语言也是吸取C++的经验,取消了多继承的语法,如Java等。

菱形继承: 属于多继承中的一种特殊情况


 

菱形继承的问题: 从下图对象成员模型构造,可以看出菱形继承有数据冗余二义性的问题。
在Assistant的对象中Person成员会有两份

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Person
  5. {
  6. public:
  7. string _name;
  8. };
  9. class Student :public Person
  10. {
  11. protected:
  12. int _num;
  13. };
  14. class Teacher :public Person
  15. {
  16. protected:
  17. int _id;
  18. };
  19. class Assistant : public Student, public Teacher
  20. {
  21. protected:
  22. string _majorCourse;
  23. };
  24. int main()
  25. {
  26. Assistant a;
  27. a.Student::_name = "bruse";
  28. a.Teacher::_name = "peter";
  29. return 0;
  30. }

可以通过显示指定访问哪个父类的成员解决二义性问题,但是数据冗余问题依然无法解决

二、解决菱形继承问题

通过虚拟继承可以解决菱形继承的二义性和数据冗余的问题

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class Person
  5. {
  6. public:
  7. string _name;
  8. };
  9. class Student :virtual public Person
  10. {
  11. protected:
  12. int _num;
  13. };
  14. class Teacher :virtual public Person
  15. {
  16. protected:
  17. int _id;
  18. };
  19. class Assistant : public Student, public Teacher
  20. {
  21. protected:
  22. string _majorCourse;
  23. };
  24. int main()
  25. {
  26. Assistant a;
  27. a._name = "bruse";
  28. a._name = "peter";
  29. return 0;
  30. }

此时问题已经解决,但对于对象模型中数据是如何存储的,我们依然不知道!

三、对象模型探究

下面将使用一个简化的菱形继承继承体系来研究虚拟继承原理

1、存在菱形继承问题的对象模型

 从上图可以清晰的看出,D类对象d中存在两份_a数据,存在数据冗余和二义性的问题

2、菱形虚拟继承的对象模型

从上图可以看出_a数据在该对象中仅存储了一份,解决了冗余的问题。

但二义性的问题又是如何解决的呢?对象模型中存储的009a7bdc和009a7be4两串十六进制数字又有什么用呢?

D类对象d中其实存在两个指针,分别属于B域和C域,009a7bdc和009a7be4两串十六进制数字便是这两个指针的值。这两个指针也被称为虚基表指针,其指向虚基表。

 虚基表中存储的是偏移量,可以通过偏移量找到唯一的_a数据。

可能会有人会发现一个问题,虚拟继承不是可以解决冗余问题吗?怎么观察上述的对象模型,使用了虚继承后怎么反而比不使用虚继承还用多上了四个字节?

解答:上述的对象模型都是通过简化的菱形继承继承体系得来的,虚基类中只存有一个int类型数据。若虚基类中存有1000个元素的int数组,不使用虚继承就会存储两份int[1000],而使用就只用存储一份。

3、B类对象模型

由上面可以看出,是否采用虚继承会影响D类对象的对象模型以及数据存储方式,那么B类和C类的对象会受到影响吗?

由上图可以看出,B类对象中也同样存在虚基表指针指向记录偏移量的虚基表

但是B类对象不存在数据的冗余和二义性,为什么对象模型也会设计成如此呢?

  1. #include<iostream>
  2. #include<string>
  3. using namespace std;
  4. class A{
  5. public:
  6. int _a;
  7. };
  8. class B :virtual public A{
  9. public:
  10. int _b;
  11. };
  12. class C :virtual public A{
  13. public:
  14. int _c;
  15. };
  16. class D : public B, public C{
  17. public:
  18. int _d;
  19. };
  20. void func(B& data) {
  21. cout << data._b << endl;
  22. }
  23. int main()
  24. {
  25. D d;
  26. d._a = 2;
  27. d._b = 3;
  28. d._c = 4;
  29. d._d = 5;
  30. B bb;
  31. bb._a = 1;
  32. bb._b = 2;
  33. func(d);
  34. func(bb);
  35. return 0;
  36. }

当出现上述代码的情况时,B类对象可以作为参数传递给func函数,D类对象也可以发生切片作为参数传递给func函数。编译器无法分辨你所传参数是B类对象还是D类对象,为了保证调用方法的一致性,B类和D类的对象模型也保持相同的设计方式。

四、继承与组合

1.public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。

2.组合是一种has-a的关系。假设B组合了A,每个B对象中都有A对象

3.继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)
术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。
派生类和基类间的依赖关系很强,耦合度高。

4.对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。
对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse)
因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。
优先使用对象组合有助于你保持每个类被封装。

5.实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有
些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承也可以用组合时,优先使用组合

五、总结

总的来说,尽量避免菱形继承的使用,大量使用菱形虚拟继承也会导致程序效率降低  (底层多了通过虚基表指针访问虚基表中记录的偏移量,再通过偏移量寻找公共数据的步骤),但也无伤大雅,毕竟现在的CPU性能也十分强大了。

在C++中文件IO的设计中便出现了菱形继承

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/977475
推荐阅读
相关标签
  

闽ICP备14008679号