在C++中,什么叫做钻石问题(也可以叫菱形继承问题),怎么避免它?

下面的图表可以用来解释钻石问题。

假设我们有类B和类C,它们都继承了相同的类A。另外我们还有类D,类D通过多重继承机制继承了类B和类C。因为上述图表的形状类似于钻石(或者菱形),因此这个问题被形象地称为钻石问题(菱形继承问题)。现在,我们将上面的图表翻译成具体的代码:

[cpp] view plaincopy
  1. /*
  2. Animal类对应于图表的类A
  3. */
  4. class Animal { /* ... */ }; // 基类
  5. {
  6. int weight;
  7. public:
  8. int getWeight() { return weight;};
  9. };
  10. class Tiger : public Animal { /* ... */ };
  11. class Lion : public Animal { /* ... */ }
  12. class Liger : public Tiger, public Lion { /* ... */ };

在上面的代码中,我们给出了一个具体的钻石问题例子。Animal类对应于最顶层类(图表中的A),Tiger和Lion分别对应于图表的B和C,Liger类(狮虎兽,即老虎和狮子的杂交种)对应于D。

现在,问题是如果我们有这种继承结构会出现什么样的问题。

看看下面的代码后再来回答问题吧。

[cpp] view plaincopy
  1. int main( )
  2. {
  3. Liger lg ;
  4. /*编译错误,下面的代码不会被任何C++编译器通过 */
  5. int weight = lg.getWeight();

在我们的继承结构中,我们可以看出Tiger和Lion类都继承自Animal基类。所以问题是:因为Liger多重继承了Tiger和Lion类,因此Liger类会有两份Animal类的成员(数据和方法),Liger对象"lg"会包含Animal基类的两个子对象。

所以,你会问Liger对象有两个Animal基类的子对象会出现什么问题?再看看上面的代码-调用"lg.getWeight()"将会导致一个编译错误。这是因为编译器并不知道是调用Tiger类的getWeight()还是调用Lion类的getWeight()。所以,调用getWeight方法是不明确的,因此不能通过编译。


钻石问题的解决方案:

我们给出了钻石问题的解释,但是现在我们要给出一个钻石问题的解决方案。如果Lion类和Tiger类在分别继承Animal类时都用virtual来标注,对于每一个Liger对象,C++会保证只有一个Animal类的子对象会被创建。看看下面的代码:

[cpp] view plaincopy
  1. class Tiger : virtual public Animal { /* ... */ };
  2. class Lion : virtual public Animal { /* ... */ }

你可以看出唯一的变化就是我们在类Tiger和类Lion的声明中增加了"virtual"关键字。现在类Liger对象将会只有一个Animal子对象,下面的代码编译正常:

[cpp] view plaincopy
  1. int main( )
  2. {
  3. Liger lg ;
  4. /*既然我们已经在Tiger和Lion类的定义中声明了"virtual"关键字,于是下面的代码编译OK */
  5. int weight = lg.getWeight();
  6. }

因为Java不支持多继承,所以不会出现菱形继承问题。但是Java可以通过接口间接实现多重继承。

[java] view plaincopy
  1. Class Mule implements Horse,Donkey
  2. {
  3. /* Horse和Donkey是接口*/
  4. }

虚继承

2.1.概念

这时候虚继承就挺身而出,扛下搞定此问题的重担了。虚继承是一种机制,类通过虚继承指出它希望共享虚基类的状态。对给定的虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象,共享基类子对象称为虚基类。虚基类用virtual声明继承关系就行了。这样一来,D就只有A的一份拷贝。如下:

[cpp] view plaincopy
  1. class A
  2. {
  3. public:
  4. A():a(1){};
  5. void printA(){cout<<a<<endl;}
  6. int a;
  7. };
  8. class B : virtual public A
  9. {
  10. };
  11. class C : virtual public A
  12. {
  13. };
  14. class D:  public B ,  public C
  15. {
  16. };
  17. int _tmain(int argc, _TCHAR* argv[])
  18. {
  19. D d;
  20. cout<<sizeof(d);
  21. d.a=10;
  22. d.printA();
  23. }

输出d的大小是12(包含了2个4字节的D类虚基指针和1个4字节的int型整数)。而a和printA()都可以正常访问。最典型的应用就是iostream继承于istream和ostream,而istream和ostream虚继承于iOS。

[cpp] view plaincopy
  1. class istream : virtual public ios{...};
  2. class ostream : virtual public ios{...};
  3. class iostream : public istream, public ostream{...};

2.2.注意

(1)支持到基类的常规转换。也就是说即使基类是虚基类,也照样可以通过基类指针或引用来操纵派生类的对象。

(2)虚继承只是解决了菱形继承中派生类多个基类内存拷贝的问题,并没有解决多重继承的二义性问题。

(3)通常每个类只会初始化自己的直接基类,如果不按虚继承处理,那么在菱形继承中会出现基类被初始两次的情况,在上例中也就是A→B→A→C→D。为了解决这个重复初始化的问题,虚继承对初始化进行了特殊处理。在虚继承中,由最底层派生类的构造函数初始化虚基类。体会一下下面这个例子:

输出构造和析构顺序:

[cpp] view plaincopy
  1. C()
  2. E()
  3. A()
  4. B()
  5. D()
  6. F()
  7. ~F()
  8. ~D()
  9. ~B()
  10. ~A()
  11. ~E()
  12. ~C()

可以看出,首先按声明顺序检查直接基类(包括其子树),看是否有虚基类,先初始化虚基类(例中首先初始化C和E)。一旦虚基类构造完毕,就按声明顺序调用非虚基类的构造函数(例中ABDF),析构的调用次序和构造调用次序相反

钻石问题(菱形继承问题) 和虚继承相关推荐

  1. c/c++入门教程 - 2.4.6 继承、公共继承、保护继承、私有继承、virtual虚继承(概念、语法、方式、构造和析构顺序、同名成员处理、继承同名静态成员处理、多继承语法、菱形继承、钻石继承)

    目录 4.6 继承 4.6.1 继承的基本语法 4.6.2 继承方式 4.6.3 继承中的对象模型 4.6.4 继承中构造和析构顺序 4.6.5 继承同名成员处理方式 4.6.6 继承同名静态成员处理 ...

  2. 继承详解(虚继承实现原理)

    继承的概念及定义 概念: ​ 继承机制是面向对象程序设计为了提高代码复用率的一种手段,它可以保持原类特性的基础上进行拓展,简单来说继承是类层次的复用. 接下来我们来看一个简单的继承 class Per ...

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

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

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

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

  5. 内存首地址为1000h_C++虚继承,菱形继承,内存分布

    前言 在叙述C++虚继承之前,我先给大家抛出一个问题.例如现在有4个类,分别是class A, class B, class C, class D.它们的关系如下图. 如上如所示,class B和cl ...

  6. C++ 菱形虚继承 通过指针来寻找继承过来的成员变量

    #define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std;//动物类 class Animal { pu ...

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

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

  8. C++:94---类继承(菱形继承、虚继承(virtual虚基类))

    一.菱形继承 在介绍虚继承之前介绍一下菱形继承 概念:A作为基类,B和C都继承与A.最后一个类D又继承于B和C,这样形式的继承称为菱形继承 菱形继承的缺点: 数据冗余:在D中会保存两份A的内容 访问不 ...

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

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

  10. 虚继承是什么意思_huaxiazhihuo

    此文只是杂乱的记录一点点对于面向对象的个人看法,有些观点也并非原创.没什么系统性可言,虽然笔者稍作整理,但始终还是显得很散乱,只是一些片段的堆积. 由于涉及的题目过于庞大,反而不知道如何下笔.先罗列一 ...

最新文章

  1. 《与编码人员一起工作》作者访谈
  2. Date 和 SimpleDateFormat 类表示时间
  3. 静态路由出接口的配置
  4. setProperty will trigger ui re-render 南京同事提的问题
  5. Dataguard学习笔记
  6. elementUI table 表格表头居中 颜色、内容居左
  7. mysql实际项目中使用多长时间_mysql常在项目中使用的语句总结
  8. 嵌入式 C 语言(上)
  9. 图片去水印工具(Teorex Inpaint) v7.1
  10. 半监督学习方法:协同训练
  11. 从技术走向管理——李元芳履职记
  12. node.js之async的使用(series,whilst)
  13. 梯形断面正常水深莫洛图
  14. 计算机硬盘容量单位换算,为什么新硬盘容量不对是什么原因?原来是硬盘容量单位换算的问题...
  15. springMvc ResultFul风格 url拼接问题
  16. html 字加白色透明,如何给网站背景加一个白色的透明文字
  17. Ubuntu 10.10 安装谷歌拼音输入法~
  18. 金三银四最新Java面试题总结前199+1页都在这里了,非常详细
  19. html -- javascript
  20. 兄弟们,我又回来了,干货太多让你们久等了

热门文章

  1. 三、CXF对Interceptor拦截器的支持
  2. Asp.net Core Configuration
  3. topcoder srm 694 div1 -3
  4. spring@Transactional注解事务不回滚不起作用无效的问题处理
  5. CSS媒体查询 @media
  6. 重新定义“物联网” GreenPeak助力合作伙伴构建智能家居
  7. python常见的特异点
  8. how-to-get-a-job-in-deep-learning
  9. 1.Hello,World all the time
  10. Office 2010 中的 UI 扩展性