一、菱形继承
- 在介绍虚继承之前介绍一下菱形继承
- 概念: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 {};
- 析构函数:析构函数的执行顺序与构造函数执行顺序相反
转载:https://blog.csdn.net/qq_41453285/article/details/104442356
查看评论