QUESTION:什么是钻石继承?

ANSWER:假设我们已经有了两个类Father1和Father2,他们都是类GrandFather的子类。现在又有一个新类Son,这个新类通过多继承机制对类Father1和Father2都进行了继承,此时类GrandFather、Father1、Father2和Son的继承关系是一个菱形,仿佛一个钻石,因此这种继承关系在C++中通常被称为钻石继承(或菱形继承)。

示意图:

示例:

 1 #include<iostream>
 2 using namespace std;
 3 class GrandFather{ //第一层基类GrandFather
 4 public:
 5     GrandFather()=default;
 6     GrandFather(int v):value(v){}
 7     int value;
 8 };
 9
10 class Father1:public GrandFather{ //第二层基类Father1
11 public:
12     Father1()=default;
13     Father1(int v):GrandFather(v){}
14     void set_value(int value){ //设置value的值
15         this->value=value;
16     }
17 };
18
19 class Father2:public GrandFather{ //第二层基类Father2
20 public:
21     Father2()=default;
22     Father2(int v):GrandFather(v){}
23     int get_value(){ //获取value的值
24         return this->value;
25     }
26 };
27
28 class Son:public Father1,public Father2{ //第三次层类Son
29 public:
30     Son()=default;
31     Son(int v):Father1(v),Father2(v){}
32 };
33
34 int main(){
35     Son s(10);
36     s.set_value(20);
37     cout<<s.get_value()<<endl;
38     return 0;
39 }

QUESTION:上例中明明将对象s的value值设置成了20,为什么最终value的输出却还是初始化值10?

ANSWER:解决这个问题我们首先需要知道对象s是如何构造的,还是上面的示例:

 1 #include<iostream>
 2 using namespace std;
 3 class GrandFather{ //第一层基类GrandFather
 4 public:
 5     GrandFather()=default;
 6     GrandFather(int v):value(v){
 7         cout<<"调用了GrandFather类的构造函数"<<endl;
 8     }
 9     int value;
10 };
11
12 class Father1:public GrandFather{ //第二层基类Father1
13 public:
14     Father1()=default;
15     Father1(int v):GrandFather(v){
16         cout<<"调用Father1类的构造函数"<<endl;
17     }
18     void set_value(int v){ //设置value的值
19         this->value=v;
20     }
21 };
22
23 class Father2:public GrandFather{ //第二层基类Father2
24 public:
25     Father2()=default;
26     Father2(int v):GrandFather(v){
27         cout<<"调用Father2类的构造函数"<<endl;
28     }
29     int get_value(){ //获取value的值
30         return this->value;
31     }
32 };
33
34 class Son:public Father1,public Father2{ //第三次子类Son
35 public:
36     Son()=default;
37     Son(int v):Father1(v),Father2(v){
38         cout<<"调用Son类的构造函数"<<endl;
39     }
40 };
41
42 int main(){
43     Son s(10);
44     s.set_value(20);
45     cout<<s.get_value()<<endl;
46     return 0;
47 }

我们发现在创建类Son的对象s时,第一层基类GrandFather的构造函数被调用了两次,这说明系统在创建对象s前会先创建两个独立的基类子对象(分别是Father1的对象和Father2的对象),然后再创建包含这两个子对象的对象s,如图:

由此可见,对象s中包含两个分属于不同子对象的成员变量value。而方法set_value()和方法get_value()虽然都是对象s的成员函数,但由于其也分属于对象s中的不同子对象,故其操作所针对的成员变量value不是同一个value,而是方法所在的子对象所包含的value,即上例中方法set_value()的功能是重新设置Father1类所创建的子对象的value值,而方法get_value()是返回Father2类所创建的子对象的value值

1 int main(){
2     Son s(10);
3     s.set_value(20);
4     cout<<"Father1类创建的子对象的value值:"<<s.Father1::value<<endl;
5     cout<<"Father2类创建的子对象的value值:"<<s.Father2::value<<endl;
6     return 0;
7 }

QUESTION:如何解决钻石继承中存在的“数据不一致”问题?

ANSWER:在C++中通常利用虚基类和虚继承来解决钻石继承中的“数据不一致”问题

特别注意:

1.什么是虚继承和虚基类

• 虚继承:在继承定义中包含了virtual关键字的继承关系

• 虚基类:在虚继承体系中通过关键字virtual继承而来的基类

2.为什么使用虚基类和虚继承

• 使用虚基类和虚继承可以让一个指定的基类在继承体系中将其成员数据实例共享给从该基类直接或间接派生出的其它类,即使从不同路径继承来的同名数据成员在内存中只有一个拷贝,同一个函数名也只有一个映射

 1 #include<iostream>
 2 using namespace std;
 3 class GrandFather{ //第一层基类GrandFather
 4 public:
 5     GrandFather()=default;
 6     GrandFather(int v):value(v){
 7         cout<<"调用了GrandFather类的构造函数"<<endl;
 8     }
 9     int value;
10 };
11
12 class Father1:virtual public GrandFather{ //第二层基类Father1,虚继承基类GrandFather
13 public:
14     Father1()=default;
15     Father1(int v):GrandFather(v){
16         cout<<"调用Father1类的构造函数"<<endl;
17     }
18     void set_value(int value){ //设置value的值
19         this->value=value;
20     }
21 };
22
23 class Father2:virtual public GrandFather{ //第二层基类Father2,虚继承基类GrandFather
24 public:
25     Father2()=default;
26     Father2(int v):GrandFather(v){
27         cout<<"调用Father2类的构造函数"<<endl;
28     }
29     int get_value(){ //获取value的值
30         return this->value;
31     }
32 };
33
34 class Son:public Father1,public Father2{ //第三次子类Son
35 public:
36     Son()=default;
37     Son(int v):Father1(v),Father2(v),GrandFather(v) {
38         cout<<"调用Son类的构造函数"<<endl;
39     }
40 };
41
42 int main(){
43     Son s(10);
44     s.set_value(20);
45     cout<<s.get_value()<<endl;
46     return 0;
47 }

上例中的钻石继承中,由于基类Father1和基类Father2采用虚继承的方式来继承类GrandFather,此时对象s中类Father1和类Father2创建的子对象共享GrandFather类创建的子对象,如图:

此时对象s中成员变量value只有一个,且被Father1类创建的子对象和Father2类创建的子对象所共享,即方法set_value()和方法get_value()操作的value是同一个成员变量。

3.构造函数的调用顺序

• 首先按照虚基类的声明顺序调用虚基类的构造函数

• 然后按照非虚基类的声明顺序调用非虚基类的构造函数

• 之后调用派生类中成员对象的构造函数

• 最后调用派生类自己的构造函数

示例:

 1 #include<iostream>
 2 using namespace std;
 3 class One{
 4 public:
 5     int one;
 6     One(int o):one(o){
 7         cout<<"调用类One的构造函数"<<endl;
 8     }
 9 };
10
11 class Two{
12 public:
13     int two;
14     Two(int t):two(t){
15         cout<<"调用类Two的构造函数"<<endl;
16     }
17 };
18
19 class Three{
20 public:
21     int three;
22     Three(int t):three(t){
23         cout<<"调用类Three的构造函数"<<endl;
24     }
25 };
26
27 class Four{
28 public:
29     Four(){
30         cout<<"调用类Four的构造函数"<<endl;
31     }
32 };
33
34 class Five{
35 public:
36     int five;
37     Five(int f):five(f){
38         cout<<"调用类Five的构造函数"<<endl;
39     }
40 };
41
42 class Six:public One,virtual Two,virtual Three,public Five{
43 public:
44     Six(int value):One(value),Two(value),Three(value) ,Five(value){ //在派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用
45         cout<<"调用类Six的构造函数"<<endl;
46     }
47 private:
48     Four four;
49 };
50
51 int main(){
52     Six six(10);
53     return 0;
54 }

4.使用虚基类和虚继承时的一些注意事项:

在派生类对象中,同名的虚基类只产生一个虚基类子对象,而同名的非虚基类则各产生一个非虚基类子对象

• 虚基类的子对象是由最后派生出来的类的构造函数通过调用虚基类的构造函数来初始化的。因此在派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用,如果没有列出,则表示使用该虚基类的默认构造函数。

• 虚基类并不是在声明基类时声明的,而是在声明派生类时通过指定其继承该基类的方式来声明的。

C++:钻石继承与虚继承相关推荐

  1. C++继承详解三 ----菱形继承、虚继承

    转载:http://blog.csdn.net/pg_dog/article/details/70175488 今天呢,我们来讲讲菱形继承与虚继承.这两者的讲解是分不开的,要想深入了解菱形继承,你是绕 ...

  2. 菱形继承和虚继承、对象模型和虚基表

    1.菱形继承(钻石继承):两个子类继承同一父类,而又有子类同时继承这两个子类.例如B,C两个类同时继承A,但是又有一个D类同时继承B,C类. 2.菱形继承的对象模型 class A { public: ...

  3. C++ 多继承和虚继承的内存布局

    原文链接:https://www.oschina.net/translate/cpp-virtual-inheritance 警告. 本文有点技术难度,需要读者了解C++和一些汇编语言知识. 在本文中 ...

  4. C++多继承与虚继承

    目录 多继承与虚继承以及存在的问题 例子 多继承与虚继承以及存在的问题 虚继承 有了多继承,虚继承才会有意义 如果有个菱形结构的继承,爷爷类为A,然后B,C是A的派生类,最后D是B和C的派生类, 如果 ...

  5. C++继承机制(三)——多继承、菱形继承、虚继承原理

    目录: C++继承机制(一)--基本语法.三种继承方式.继承哪些数据 C++继承机制(二)--继承中的构造和析构顺序.继承同名成员的处理方式 C++继承机制(三)--多继承.菱形继承.虚继承原理 本篇 ...

  6. C++对象模型:单继承,多继承,虚继承

    什么是对象模型 有两个概念可以解释C++对象模型: 语言中直接支持面向对象程序设计的部分. 对于各种支持的底层实现机制. 类中成员分类 数据成员分为静态和非静态,成员函数有静态非静态以及虚函数 cla ...

  7. C++之菱形继承与虚继承(含虚函数)

    面向对象的三大特征:封装,多态,继承 前面我们已经讲了继承的一些知识点,在这基础上,我们讲的时候再涉猎一些多态的只是. 下面我们先接着上次讲有虚函数的菱形虚继承 首先什么是虚函数.? 虚函数:在类里面 ...

  8. C++57个入门知识点_50 菱形继承与虚继承(C++中语法允许多重继承造成菱形继承;会造成近亲结婚的问题;可以通过虚继承的方式解决;实际项目中不多用多重继承)

    上篇C++57个入门知识点_49 多重继承与组合(一个类同时具有多个类的属性的方法:多重继承或者组合:多重继承:一个类同时继承多个类:多重继承构造和析构的顺序与普通继承类似:组合:类中包含多个成员对象 ...

  9. C++普通继承和虚继承详解

    继承 继承概念 所谓的继承就是一个类继承了另一个类的属性和方法,这个新的类包含了上一个类的属性和方法,被称为子类或者派生类,被继承的类称为父类或者基类. 继承特点 子类拥有父类的所有属性和方法(除了构 ...

  10. C++菱形继承与虚继承

    菱形继承 菱形继承引入的问题 菱形继承的解决 虚继承的逻辑 菱形继承 不合理的地方 查看类布局的方式:使用命令 cl 菱形继承的构造 虚继承 虚基类 虚继承构造 虚继承的作用 菱形继承引入的问题 造成 ...

最新文章

  1. 汇编语言课本习题 p112 3.30
  2. 军事医学研究院应晓敏组招聘博士后
  3. ZooKeeper系列(四)
  4. PowerShell因为在此系统中禁止执行脚本解决方法
  5. Eclipse开发程序,取得新的工程后,启动Web服务出错原因总结
  6. JAVA多线程----用--取钱问题1
  7. 史上超级详细:银行外包java面试题目
  8. 洛谷P6685 可持久化动态仙人掌的直径问题
  9. VelocityEngine 和Velocity类解析
  10. CSMA/CD协议 详解
  11. 如何把HTML转换成动图,视频转gif 如何将视频制作gif动画图片
  12. docker的容器下使用apt-get update卡在[0%] Working问题解决办法
  13. 虚拟语气用法总结及真题解析
  14. c/c++中sizeof()、strlen()、length()、size()详解和区别
  15. css 一些好玩的属性,css一些不常见但很有用的属性操作大全
  16. 计算机硬件维护的注意事项有哪些,计算机的硬件维护注意事项
  17. 从神经元到CNN、RNN、GAN…基础神经网络模型原理概述
  18. BSN长话短说之一:万字庖解区块链跨链技术
  19. linux软件不能通过验证,Linux上安装软件之前先验证软件包合法性
  20. 小米大BOSS雷军写Java代码水平如何?一起来扒一扒

热门文章

  1. 【转载】使用python进行ABAQUS的二次开发的简要说明(by Young 2017.06.27)
  2. Python 3爬虫网易云(八)—— 对网易云歌词的爬取
  3. 微信小程序实现lot开发01 学习微信小程序 helloworld
  4. Cocos2dxActivity报错
  5. android开发Enum (枚举)的更轻量级的替代方案 —— @IntDef的使用
  6. 双电机功率分汇流电动拖拉机再生制动策略
  7. 2.4.5 Python存储之表格
  8. SQL练习题:连续登录5天的活跃用户
  9. Spark 常用 API
  10. 字符指针分拣C语言,一种货物自动分拣方法及系统与流程