一、菱形继承

* 在介绍虚继承之前介绍一下菱形继承
* 概念:A作为基类,B和C都继承与A。最后一个类D又继承于B和C,这样形式的继承称为菱形继承
* 菱形继承的缺点:
* 数据冗余:在D中会保存两份A的内容
* 访问不明确(二义性):因为D不知道是以B为中介去访问A还是以C为中介去访问A,因此在访问某些成员的时候会发生二义性
* 缺点的解决:
* 数据冗余:通过下面“虚继承”技术来解决(见下)
* 访问不明确(二义性):通过作用域访问符::来明确调用。虚继承也可以解决这个问题

演示案例
class A { public: A(int a) :m_a(a) {} int getMa() { return m_a; } private: int
m_a; }; class B :public A { public: B(int a, int b) :A(a), m_b(b) {} private:
int m_b; }; class C :public A { public: C(int a, int c) :A(a), m_c(c) {}
private: int m_c; }; class D :public B, public C { public: D(int a, int b, int
c, int d) :B(a, b), C(a, c), m_d(d) {} void func() { /*错误,访问不明确 std::cout <<
getMa();*/ //正确,通过B访问getMa() std::cout << B::getMa(); } private: int m_d; };
二、虚继承

* 虚继承的作用:为了保证公共继承对象在创建时只保存一分实例
* 虚继承解决了菱形继承的两个问题:
* 数据冗余:顶级基类在整个体系中只保存了一份实例
* 访问不明确(二义性):可以不通过作用域访问符::来调用(原理就是因为顶级基类在整个体系中只保存了一份实例)
* 共享的基类对象成为“虚基类”
* 说明:虚继承不会影响派生类本身,只是对虚基类进行的说明
* 通过在继承列表中使用virtual关键字来说明,virtual与继承说明符(public、protected、private)的位置可以互换
演示案例

* 下面的ZooAnimal是一个虚基类,Bear和Raccoon分别虚继承于ZooAnimal

class ZooAnimal {}; //虚基类 class Bear :public virtual ZooAnimal {}; //虚继承 class
Raccoon :public virtual ZooAnimal {}; //虚继承 //Panda只保存一份ZooAnimal的定义 class
Panda :public Bear, public Raccoon, public Endangered {};
三、虚继承中的类型转换

* 虚继承中也可以将派生类抓换为基类,用基类的指针/引用指向于派生类
* 例如: class ZooAnimal {}; class Bear :public virtual ZooAnimal {}; class
Raccoon :public virtual ZooAnimal {}; class Panda :public Bear, public Raccoon,
public Endangered {}; void dance(const Bear&); void rummage(const Raccoon&);
ostream& operator<<(ostream&, const ZooAnimal&); int main() { Panda ying_yang;
dance(ying_yang); //正确,把一个Panda对象当成Bear传递
rummage(ying_yang);//正确,把一个Panda对象当成Raccoon 传递 cout << ying_yang;
//正确,把一个Panda对象当成ZooAnimal传递 return 0; }
四、虚基类成员的可见性与隐藏

* 规则如下:
* 虚基类的成员没有被任何派生类隐藏,那么该成员可以直接访问,并且不会产生二义性
* 如果虚基类的成员只被一条派生路径隐藏,则我们仍然可以直接访问这个被隐藏的版本
* 如果虚基类的成员多多个派生路径隐藏,则会产生二义性
* 例如,D1和D2虚继承与B,D继承于D1和D2,并且B有一个x成员:
* 如果D1和D2都没有x的定义:此时对x的访问不会产生二义性,因为只含有x的一个实例
* 如果D1中有x的定义而D2没有:同样没有二义性,派生类的x比虚基类B的x优先级更高(或者D1中没有x的定义而D2有x的定义)
* 如果D1和D2都有x的定义:对x的访问会产生二义性

* 解决二义性最好的办法就是在派生类为成员自定义新的实例
五、虚继承的构造函数

* 虚继承中的构造函数与普通继承的构造函数不一样:
* 普通继承:派生类可以不为间接基类(基类的基类)进行构造函数的调用
* 虚继承:不论派生类属于哪一层,派生类都需要对虚基类进行构造
* 原因:
假设以下间接派生类没有为虚基类进行构造,那么当间接派生类进行构造时,会对虚基类进行重复的构造函数的调用(例如下面的演示案例D如果不显式构造A,那么当构造B和C的时候,B和C都会构造一次A,从而造成错误)。因此我们需要在间接派生类中为虚基类进行构造,从而避免了重复构造的二义性
演示案例
//普通继承 class A { public: A(int a); }; class B :public A { public: B(int
a):A(10) {} }; class C :public B { public: C() :B(10) {}
//可以不为A进行构造,因为A的构造已经交给B了 }; //虚继承 class A { public: A(int a); }; class B
:virtual public A { public: B(int a):A(10) {} }; class C :virtual public A {
public: C(int a) :A(10) {} }; class D :public B,public C { public: //D()
:B(10), C(20) {} 错误的,必须显式为A进行构造 D() :A(5), B(10), C(20) {} //正确 };
构造函数的执行顺序

* 规则:虚基类总是先于非虚基类构造,与它们在继承体系中的次序和位置无关
* 例如,在上面的演示案例中,构造顺序为:A-->B-->C-->D
* 下面再演示一个有多个虚基类的例子,其构造函数执行熟悉怒为:
* ZooAnimal
* ToyAnimal
* Character
* BookCharacter
* Bear
* TeddyBear

class Character {}; class BookCharacter :public Character {}; class ZooAnimal
{}; class Bear :public virtual ZooAnimal {}; class ToyAnimal {}; class
ReddyBear :public BookCharacter, public Bear, public virtual ToyAnimal {};
* 析构函数:析构函数的执行顺序与构造函数执行顺序相反

技术
©2019-2020 Toolsou All rights reserved,
PYTHON入门期末复习汇总Python+OpenCV人脸识别技术详解员工网上晒腾讯年终奖:每人100股公司股票 价值超6万元!中国最长高铁正式开通!跑完全程最快30.5小时Unity 场景异步加载(加载界面的实现)NOI2019 游记用C++跟你聊聊“原型模式” (复制/拷贝构造函数)比尔·盖茨:疫情后彻底恢复正常可能要到2022年末这些歌,程序员千万万万万别听!随机森林篇 R语言实现