《C++ Primer》读书笔记——多重继承
18.3.1
class Bear : public ZooAnimal{/*...*/}; class Panda : public Bear, public Endangered{/*...*/}
多重继承的构造函数的执行顺序。

每个基类对应一个访问控制符,某个基类没写的话,struct 默认 public 继承,class 默认private继承。
不能重复继承,如
struct Dreived : public Base, public Base { };
多重继承类的构造函数
Panda::Panda(std::string name, bool onExhibit) : Bear(neme, onExhibit, "Panda"), Endangered(Endangered::critical) { }
也可以隐式地使用 Bear 的默认构造函数初始化 Bear 子对象
Panda::Panda():
: Endangered(Endangered::critical) { }
基类的构造顺序与 派生列表中基类的出现顺序保持一致,而与派生类构造函数初始值列表中基类的顺序无关。
含有多个基类和含有多个子对象的类的构造函数执行顺序是:先按照派生列表的顺序构造基类,再按照类中声明的子对象顺序构造子对象,最后按照类中数据成员在类中的声明顺序初始化数据成员。
当一个派生类继承了多个基类,且这些基类有相同的构造函数(参数列表相同),则如果派生类想从基类处继承构造函数,派生类需要重新定义该构造函数。
struct Base1 { int b1_x; Base1(int b1):b1_x(b1){}; }; struct Base2 { int b2_x; Base2(int b2):b2_x(b2){}; }; struct Derived : public Base1, public Base2 { int d_x; using Base1::Base1; //从Base1继承构造函数 using Base2::Base2; //从Base2继承构造函数
// 如果写了上面两行,下面必须重新定义Derived类的构造函数,否则出错(二义性?)。
Derived(int d) : Base1(d), Base2(d) {} //重新定义了自己的版本 Derived(int a, int b, int c) : Base1(a), Base2(b) { d_x = c; } Derived() = default; // 不能没有默认构造函数 };
析构函数的调用顺序正好和构造函数相反。
18.3.2
当派生类有两个以上基类时,要注意容易引发二义性错误。
struct Base1 { }; struct Base2 { }; struct Derived : public Base1, public Base2 { }; void func(Base1& b){ } void func(Base2& b){ } int main() { Derived d; func(d); //二义性错误 return 0; }
struct Derived : public Base1, public Base2 { }; //Base1 和 Base2 没有其他关联
当我们用 Base1 类的指针或引用去访问一个 Derived 对象时,Derived 中 Derived 特有的部分以及 Base2 的部分都是不可见的。
18.3.3
在多重继承中,名字查找沿着 派生类—>基类 的方向进行,知道找到所需的名字,派生类的名字将隐藏基类的同名成员。 相同的查找过程在所有直接基类中同时进行。如果名字在多个基类中都被找到,则对改名字的使用将具有二义性。
可以把多继承体系看做一棵倒立的多叉树。叶子为基类,根为最终的派生类 ,名字查抄是从根到叶子的方向进行。

struct Base1 { void f(){}; }; struct Base2 { void f(){}; }; struct Derived : Base1, Base2 { }; int main() { Derived *d = new Derived; d->f(); //二义性错误 d->Base1::f(); //正确 d->Base2::f(); //正确 return 0; }
如上所见,调用f()会引发二义性错误。另一种情况是,有时即使派生类继承的两个函数形参列表不一样也可能发生错误,其中一个函数设为批private也会发生错误,总之只要在两条继承链中出现同样的函数名称,不管公有私有、参数列表怎么样,都会出现二义性错误。
我们可以为Derived定义一个f()解决二义性问题。
struct Base1 { void f(){}; }; struct Base2 { void f(){}; }; struct Derived : Base1, Base2 { void f() //解决二义性问题 { Base1::f(); Base2::f(); } };
18.3.4
使用虚基类
struct Base0{}; struct Base1 : public virtual Base0 {}; //virtual 和 public 的顺序随意 struct Base2 : public virtual Base0 {};
virtual 说明符表明了,在后续的派生类中可以共享基类的同一份实例。
struct Base0{int x = 10;}; struct Base1 : public virtual Base0 {}; //virtual 和 public 的顺序随意 struct Base2 : public virtual Base0 {}; struct Derived : public Base1, public Base2 {}; int main() { Derived d; d.Base2::x = 60; cout << d.Base1::x << endl; //显示60说明共享一份Base0 实例,如果把上面的virtual删去,则显示10 return 0; }
显示60说明共享一份Base0 实例,如果把上面的virtual删去,则显示10。
Derived中只有一个基类部分。
一个基类被继承时别声明为虚基类不影响自身,只影响他派生出去的后面的类对他的使用。
虚基类成员的可见性。
- 如果 Base1 和 Base2 都没有名为x的成员,Base1和Base2都是从Base0继承得到,此时不存在二义性。
- 如果 Base1 或 Base2 中只有一个名为 x 的成员,那么也不存在二义性,优先使用 Base1/Base2 中的x。
- 如果 Base1 和 Base2 中都有名为 x 的成员,那么会引发二义性。因为有两个x的优先级一样。
总结:有两个名字优先级一样的就会引发二义性。
struct Base0{int x = 10;}; struct Base1 : public virtual Base0 {int x = 20;}; struct Base2 : public virtual Base0 {}; struct Derived : public Base1, public Base2 {}; Derived d; cout << d.x << endl; //正确,显示20.说明虚基类的成员在名字查找时优先级比较低
struct Base0{int x = 10;}; struct Base1 : public virtual Base0 {int x = 20;}; struct Base2 : public virtual Base0 {int x = 30;}; struct Derived : public Base1, public Base2 {}; Derived d; cout << d.x << endl; //二义性错误
18.3.5 构造函数与虚继承。
struct Base0 { Base0() = default; }; struct Base1 : public virtual Base0 { Base1() = default; }; struct Derived : public Base1 { Derived(): Base0(), Base1(){} //因为 Base0 是一个虚基类,即使 Base0 不是 Derived 的直接基类,
//也能在这里调用 Base0() 的构造函数。如果不是虚基类就会报错。 };
虚基类的构造函数可被其非直接派生类调用。而且必须显式调用继承连中的所有虚基类,否则会调用该基类的默认构造函数。和普通继承的连续调用基类构造函数不一样。
struct Base0 { int x, y; Base0():x(999), y(888) {}; Base0(int xx, int yy): x(xx), y(yy){} }; struct Base1 : public virtual Base0 //虚继承 Base0 { Base1(int xx, int yy):Base0(xx, yy){}; Base1() = default; }; struct Derived : public Base1 { // 没有显式调用Base0(),所以Base0()的构造函数用的是默认构造函数 // 如果改成() Derived(int xx, int yy): Base0(xx, yy){} // 才能打印出 1 2 Derived(int xx, int yy): Base1(xx, yy){} //这里没有显式调用Base0 }; Derived d(1, 2); cout << d.x << " " << d.y << endl; //显式 999 888
要显式调用 父类的父类 的构造函数。否则,会使用 父类的父类 的默认构造函数。
假设有以下继承关系:TeddyBear 是最终的派生类。

class Character{ /*...*/ }; class BookCharacter : public Character { /*...*/ }; class ToyAnimal { /*...*/ }; class TeddyBear : public BookCharacter, public Bear, public virtual ToyAnimal { /*...*/ };
编译器按照基类的声明顺序对其以此进行检查,以确定其中是否有虚基类。如果有,则先构造虚基类,然后按照生命的顺序逐一构造其他非虚基类。因此,要想创建一个TeddyBear对象,需要按照如下次序调用这些构造函数。
ZooAnimal(){} //派生列表从左到右第一个虚基类
ToyAnimal(){} //派生列表从左到右第二个虚基类
Character(){} //第一个非虚基类的简介基类
BookCharacter(){} //第一个直接非虚基类
Bear(){} //第二个直接非虚基类
TeddyBear(){} //最接近自身的类
浙公网安备 33010602011771号