1.Cpp中的虚继承与虚基类

  • 在多继承时,很容易产生命名冲突的问题,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如典型的是菱形继承,如下图所示:
  • 类A派生出类B和类C,类D继承自类B和类C,这个时候类A中的成员变量和成员函数继承到类D中变成了两份,一份来自A–>B–>D这条路径,另一份来自A–>C–>D这条路径。在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突。假如类A有一个成员变量a,那么在类D中直接访问a 就会产生歧义,编译器不知道它究竟来自A -->B–>D这条路径,还是来自A–>C–>D这条路径。下面是菱形继承的具体实现:
        #include "iostream"using namespace std;// 间接基类class A{protected:int m_a;};// 直接基类Bclass B : public A{protected:int m_b;};// 直接基类Cclass C : public A{protected:int m_c;};// 派生类Dclass D : public B, public C{public:// void seta(int a) { m_a = a; }   命名冲突,为了解决命名冲突,可以使用void B::seta(int a){m_a = a;}void setb(int b) { m_b = b; }void setc(int c) { m_c = c; }private:int m_d;};int main(){D d;return 0;}
    

2.虚继承

  • 为了解决多继承时的命名冲突和冗余数据问题,C++提出了虚继承,使得在派生类中只保留一份间接基类的成员。在继承方式前面加上virtual关键字就是虚继承,请看下面的例子:

        #include "iostream"using namespace std;// 间接基类class A{protected:int m_a;};// 直接基类Bclass B : virtual public A   // 加上关键字virtual!{protected:int m_b;};// 直接基类Cclass C : virtual public A{protected:int m_c;};// 派生类Dclass D : public B, public C{public:void seta(int a) { m_a = a; }   // 正确!void setb(int b) { m_b = b; }void setc(int c) { m_c = c; }private:int m_d;};int main(){D d;return 0;}
    
  • 虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class),本例中的A就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。重新梳理一下本例的继承关系,如下图所示:
  • 观察这个新的继承体系,我们会发现虚继承的一个不太直观的特征:必须在虚派生的真实需求出现前就已经完成虚派生的操作。在上图中,当定义D类时才出现了对虚派生的需求,但是如果B类和C类不是从A类虚派生得到的,那么D类还是会保留A类的两份成员。换个角度讲,虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身

3.虚基类成员的可见性

  • 因为在虚继承的最终派生类中只保留了一份虚基类的成员,所以该成员可以被直接访问,不会产生二义性。此外,如果虚基类的成员只被一条派生路径覆盖,那么仍然可以直接访问这个被覆盖的成员。但是如果该成员被两条或多条路径覆盖了,那就不能直接访问了,此时必须指明该成员属于哪个类。
  • 以图2中的菱形继承为例,假设B定义了一个名为x的成员变量,当我们在D中直接访问x时,会有三种可能性:
    • 如果B和C中都没有x的定义,那么x将被解析为B的成员,此时不存在二义性。
    • 如果B或C其中的一个类定义了x,也不会有二义性,派生类的x比虚基类的x优先级更高。
    • 如果B和C中都定义了x,那么直接访问x将产生二义性问题。

4.虚继承时的构造函数

  • 在虚继承中,虚基类是由最终的派生类初始化的。换句话说,最终派生类的构造函数必须要调用虚基类的构造函数。对最终的派生类来说,虚基类是间接基类,而不是直接基类。这跟普通继承不同,在普通继承中,派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的

        // 虚基类AAclass AA{protected:int m_a;public:AA(int a);};// 类外定义虚基类AA的构造函数AA::AA(int a):m_a(a){}// 直接派生类BBclass BB: virtual public AA{protected:int m_b;public:BB(int a, int b);void show();};BB::BB(int a, int b): AA(a), m_b(b){}void BB::show(){cout << "m_a = " << m_a << ",m_b = " << m_b << endl;}// 直接派生类CCclass CC: virtual public AA{public:CC(int a, int c);void show();protected:int m_c;};CC::CC(int a, int c):AA(a), m_c(c){}void CC::show(){cout << "m_a = " << m_a << ",m_c = " << m_c << endl;}// 间接派生类DDclass DD: public BB, public CC{protected:int m_d;public:DD(int a, int b, int c, int d);void show();};DD::DD(int a, int b, int c, int d):AA(a), BB(90, b), CC(100, c), m_d(d){}void DD::show(){cout <<"m_a = " << m_a << ",m_b = " << m_b << ",m_c = " << m_c << ",m_d = " << m_d << endl;}int main(){BB bb(10, 20);bb.show();CC cc(30, 40);cc.show();DD dd(50, 60, 70, 80);dd.show();return 0;}
    
  • 在最终派生类DD的构造函数中,除了调用BB和CC的构造函数,还调用了AA的构造函数,这说明DD不但要负责初始化直接基类BB和CC,还要负责初始化间接基类AA。而在以往的普通继承中,派生类的构造函数只负责初始化它的直接基类,再由直接基类的构造函数初始化间接基类,用户尝试调用间接基类的构造函数将导致错误。
  • 现在采用了虚继承,虚基类AA在最终派生类DD中只保留了一份成员变量m_a,如果由BB和CC初始化m_a,那么BB和CC在调用AA的构造函数时很有可能给出不同的实参,这个时候编译器就会犯迷糊,不知道使用哪个实参初始化m_a。为了避免出现这种矛盾的情况,C++干脆规定必须由最终的派生类DD来初始化虚基类AA,直接派生类BB和CC对AA的构造函数的调用是无效的。在代码中,调用BB的构造函数时试图将m_a初始化为90,调用CC的构造函数时试图将m_a初始化为100,但是输出结果有力地证明了这些都是无效的,m_a最终被初始化为50,这正是在DD中直接调用AA的构造函数的结果。
  • 另外,需要关注的是构造函数的执行顺序。虚继承时构造函数的执行顺序与普通继承时不同:在最终派生类的构造函数调用列表中,不管各个构造函数出现的顺序如何,编译器总是先调用虚基类的构造函数,再按照出现的顺序调用其他的构造函数;而对于普通继承,就是按照构造函数出现的顺序依次调用的

C++中的虚继承与虚基类相关推荐

  1. C++虚继承和虚析构函数

    虚继承 当一个基类被声明为虚基类后,即使它成为了多继承链路上的公共基类,最后的派生类中也只有它的一个备份.例如: class CBase { }: class CDerive1:virtual pub ...

  2. C++中的各种“虚“-- 虚函数、纯虚函数、虚继承、虚基类、虚析构、纯虚析构、抽象类讲解

    C++中的各种"虚" 1. 菱形继承 1.1 虚继承 && 虚基类 1.2 虚基类指针(vbptr)&& 虚基类表(vbtable) 2. 多态 2 ...

  3. C++虚继承和虚基类详解(二)

    虚继承(Virtual Inheritance) 为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员. 在继承方式前面加上 virtual 关键字就 ...

  4. 多重继承、虚继承与虚基类

    一.多重继承 单重继承--一个派生类最多只能有一个基类 多重继承--一个派生类可以有多个基类 class 类名: 继承方式 基类1,继承方式 基类2,-. {-.}; 派生类同时继承多个基类的成员,更 ...

  5. 什么是虚继承?虚基类?

    虚拟继承是多重继承中特有的概念.虚拟基类是为解决多重继承而出现的. 类D继承自B类和C类,而B类和C类都继承自类A,因此出现下图所示情况: A          A \          / B   ...

  6. C++虚继承和虚基类;虚函数与继承

    ref http://blog.csdn.net/owen7500/article/details/52432970?locationNum=4&fps=1 http://blog.csdn. ...

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

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

  8. C++虚继承和虚基类

    多继承(Multiple Inheritance)是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员.尽管概念上非常简单,但是多个基类的相互交织可能会带来错综复杂的设计问题,命名 ...

  9. 【C++】继承和派生、虚继承和虚基类、虚基类表和虚基类指针

    继承和派生.虚继承和虚基类.虚基类表和虚基类指针 继承和派生 继承概述 继承基本概念 派生类中的成员 继承的内容 派生类定义 派生类访问控制 对象构造和析构 对象构造和析构的调用顺序 继承中的构造和析 ...

最新文章

  1. HDU——1498 50 years, 50 colors
  2. 深度学习框架的评估与比较
  3. mybatis 自定义函数_JDK动态代理一定要有代理对象吗?请你结合Mybatis回答
  4. 【Machine Learning实验1】batch gradient descent(批量梯度下降) 和 stochastic gradient descent(随机梯度下降)
  5. python中count的作用_python中函数COUNT()的功能是什么
  6. gdb调试多进程和多线程命令
  7. 第二次作业动手动脑的解答
  8. HDU - 1087
  9. GDAL写入FileGDB中文属性乱码问题
  10. Webgl(ThreeJS)空间测量\测距功能(附工程文件)
  11. 铁路订票系统的简单设计(转)
  12. 坦然面对,大步向前!
  13. Reducing Participation Costs via Incremental Verification for Ledger Systems学习笔记
  14. 字体侵权太严重,我准备了700款可商用字体
  15. 控制系统分析常用命令
  16. C++学习 - lambada表达式
  17. Java常用的几种JSON解析工具
  18. 关于集成第三方微信支付,支付宝,高德地图等排坑
  19. 华为下矿不挖煤,鸿蒙搭台不唱戏
  20. java计算机毕业设计水果销售管理网站源码+系统+数据库+lw文档

热门文章

  1. jquery.min.map 404 (Not Found)出错的原因及解决办法
  2. Dsquery和Csvde命令的使用
  3. 【079】用代码来创建 Android 控件
  4. 更改windows域名
  5. 领域驱动设计(DDD)架构演进和DDD的几种典型架构介绍(图文详解)
  6. 推荐几个开源类库,超好用,远离996!
  7. 2万长文,一文搞懂Kafka
  8. 面试题:mysql 一棵 B+ 树能存多少条数据?
  9. 矮个男生不好找对象?某大厂程序员自称太高也难找对象!身高196cm,有房有车,却被嫌太高!...
  10. 亿级流量搜索前端,是怎么做架构升级的?