《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(){}      //最接近自身的类

 

posted on 2017-04-06 16:16  nclyb  阅读(199)  评论(0)    收藏  举报

导航