C++多重继承

大多数应用程序使用单个基类的公用继承,但是在某些情况下,需要从多于一个直接基类派生类,也就是所谓的多重继承,多重继承的派生类继承其所有父类的属性。

1:多重继承的例子:

class Bear : public ZooAnimal {
};
class Panda : public Bear, public Endangered {
};

派生类为每个基类(显式或隐式地)指定了访问级别——public、protected 或private。

2:在多重继承下,派生类的对象包含每个基类的基类子对象。当构造一个Panda对象的时候,该对象包含一个 Bear 类子对象(Bear 类子对象本身包含一个 ZooAnimal 基类子对象)、一个 Endangered 类子对象以及 Panda 类中声明的非 static 数据成员(如果有的话),如下图:

3:构造派生类的对象包括构造和初始化所有基类子对象。派生类的构造函数可以在构造函数初始化式中给零个或多个基类传递值:

// explicitly initialize both base classes
Panda::Panda(std::string name, bool onExhibit)
: Bear(name, onExhibit, "Panda"),Endangered(Endangered::critical)
{ }

// implicitly use Bear default constructor to initialize the Bear subobject
Panda::Panda(): Endangered(Endangered::critical)
{ }

  

构造函数初始化式只能控制用于初始化基类的值,不能控制基类的构造次序。基类构造函数按照基类在类派生列表中的出现次序调用。对 Panda 而言,基类初始化的次序是:

a. ZooAnimal,从 Panda 的直接基类 Bear 沿层次向上的最终基类。

b. Bear,第一个直接基类。

c. Endangered,第二个直接基类,它本身没有基类。

d. Panda,初始化 Panda 本身的成员,然后运行它的构造函数的函数体。

总是按构造函数运行的逆序调用析构函数。在我们的例子中,调用析构函数的次序是 ~Panda, ~Endangered, ~Bear, ~ZooAnimal。

4:在多重继承情况下,遇到二义性转换的可能性更大。例如,如果有 print 函数的重载版本:

void print(const Bear&);
void print(const Endangered&);
Panda ying_yang("ying_yang");
print(ying_yang); // error: ambiguous

导致一个编译时错误,指出该调用是二义性的。

5:假定所有根基类都将它们的析构函数适当定义为虚函数,那么,无论通过哪种指针类型删除对象,虚析构函数的处理都是一致的:

// each pointer points to a Panda
delete pz; // pz is a ZooAnimal*
delete pb; // pb is a Bear*
delete pp; // pp is a Panda*
delete pe; // pe is a Endangered*

假定这些指针每个都向 Panda 对象,则每种情况下发生完全相同的析构函数调用次序。析构函数调用的次序是构造函数次序的逆序:通过虚机制调用 Panda 析构函数。随着 Panda 析构函数的执行,依次调用 Endangered、Bear 和ZooAnimal 的析构函数。

像单继承的情况一样,如果具有多个基类的类定义了自己的析构函数,该析构函数只负责清除派生类。如果派生类定义了自己的复制构造函数或赋值操作符,则类负责复制(赋值)所有的基类子部分。只有派生类使用复制构造函数或赋值操作符的合成版本,才自动复制或赋值基类部分。

6:在多重继承下,名字查找同时检察所有的基类继承子树——在我们的例子中,并行查找Endangered 子树和 Bear/ZooAnimal 子树。如果在多个子树中找到该名字,则那个名字的使用必须显式指定使用哪个基类;否则,该名字的使用是二义性的。

假定 Bear 类和 Endangered 类都定义了名为 print 的成员,如果 Panda 类没有定义该成员,则ying_yang.print(cout);这样的语句将导致编译时错误。

派生只是导致潜在的二义性,如果没有 Panda 对象调用 print,就可以避免这个二义性。如果每个 print 调用明确指出想要哪个版本——Bear::print 还是Endangered::print,也可以避免错误。只有在存在使用该成员的二义性尝试的时候,才会出错。

即使两个继承的函数有不同的形参表,也会产生错误。类似地,即使函数在一个类中是私有的而在另一个类中是公用或受保护的,也是错误的。

可以通过指定使用哪个类解决二义性:ying_yang.Endangered::print(cout);

7:在多重继承下,一个基类可以在派生层次中出现多次。比如IO 库类:

多重继承的类从它的每个父类继承状态和动作,如果 IO 类型使用常规继承,则每个 iostream 对象可能包含两个 ios 子对象:一个包含在它的 istream 子对象中,另一个包含在它的 ostream 子对象中,从设计角度讲,这个实现正是错误的。

使用虚继承解决这类问题。虚继承是一种机制,类通过虚继承指出它希望共享其虚基类的状态。在虚继承下,对给定虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象。

istream 和 ostream 类对它们的基类进行虚继承。通过使基类成为虚基类,istream 和 ostream 指定,如果其他类(如 iostream )同时继承它们两个,则派生类中只出现它们的公共基类的一个副本。通过在派生列表中包含关键字virtual 设置虚基类:

class istream : public virtual ios { ... };
class ostream : virtual public ios { ... };
class iostream: public istream, public ostream { ... };

8:考虑下面的例子:

class Panda : public Bear, public Raccoon, public Endangered {
};

必须在提出虚派生的任意实际需要之前进行虚派生(在例中,Bear类和 Raccoon 类的虚派生)。只有在使用 Panda 的声明时,虚继承才是必要的。

通过用关键字 virtual 修改声明,将基类指定为通过虚继承派生。例如,下面的声明使 ZooAnimal 类成为 Bear 类和 Raccoon 类的虚基类:

// the order of the keywords public and virtual is not significant
class Raccoon : public virtual ZooAnimal { /* ... */ };
class Bear : virtual public ZooAnimal { /* ... */ };

9:假定通过多个派生路径继承名为 X 的成员,有下面三种可能性:

a. 如果在每个路径中 X 表示同一虚基类成员,则没有二义性,因为共享该成员的单个实例。

b. 如果在某个路径中 X 是虚基类的成员,而在另一路径中 X 是后代派生类的成员,也没有二义性——特定派生类实例的优先级高于共享虚基类实例。

c. 如果沿每个继承路径 X 表示后代派生类的不同成员,则该成员的直接访问是二义性的。

10:每个类只初始化自己的直接基类。在应用于虚基类的情况,这个初始化策略会失败。如果使用常规规则,就可能会多次初始化虚基类。类将沿着包含该虚基类的每个继承路径初始化。在 ZooAnimal 示例中,使用常规规则将导致 Bear类和 Raccoon 类都试图初始化 Panda 对象的 ZooAnimal 类部分。

为了解决这个重复初始化问题,从具有虚基类的类继承的类对初始化进行特殊处理。在虚派生中,由最低层派生类的构造函数初始化虚基类。在我们的例子中,当创建 Panda 对象的时候,只有 Panda 构造函数控制怎样初始化 ZooAnimal基类。

虽然由最低层派生类初始化虚基类,但是任何直接或间接继承虚基类的类一般也必须为该基类提供自己的初始化式。只要可以创建虚基类派生类类型的独立对象,该类就必须初始化自己的虚基类,这些初始化式只有创建中间类型的对象时使用。

在我们的层次中,可以有 Bear、Raccoon 或 Panda 类型的对象。创建 Panda 对象的时候,它是最低层派生类型并控制共享的 ZooAnimal 基类的初始化:创建Bear 对象(或 Raccoon 对象)的时候,不涉及更低层的派生类型。在这种情况下,Bear(或 Raccoon)构造函数像平常一样直接初始化它们的 ZooAnimal 基类:

Bear::Bear(std::string name, bool onExhibit): ZooAnimal(name, onExhibit, "Bear") { }
Raccoon::Raccoon(std::string name, bool onExhibit): ZooAnimal(name, onExhibit, "Raccoon") { }

虽然 ZooAnimal 不是 Panda 的直接基类,但是 Panda 构造函数也初始化ZooAnimal 基类:

Panda::Panda(std::string name, bool onExhibit):
    ZooAnimal(name, onExhibit, "Panda"),
    Bear(name, onExhibit),
    Raccoon(name, onExhibit),
    Endangered(Endangered::critical),
    sleeping_flag(false)
{ }

Bear winnie("pooh"); // Bear constructor initializes ZooAnimal
Raccoon meeko("meeko"); // Raccoon constructor initializes ZooAnimal
Panda yolo("yolo"); // Panda constructor initializes ZooAnimal

当创建 Panda 对象的时候,

首先使用构造函数初始化列表中指定的初始化式构造 ZooAnimal 部分;接下来,构造 Bear 部分。忽略 Bear 的用于 ZooAnimal 构造函数初始化列表的初始化式;然后,构造 Raccoon 部分,再次忽略 ZooAnimal 初始化式;最后,构造 Panda 部分。

11:无论虚基类出现在继承层次中任何地方,总是在构造非虚基类之前构造虚基类。

下面TeddyBear的派生中,有两个虚基类:ToyAnimal基类和派生 Bear 的间接基类 ZooAnimal:

class Character { /* ... */ };
class BookCharacter : public Character { /* ... */ };
class ToyAnimal { /* ... */ };
class TeddyBear: public BookCharacter, public Bear, public virtual ToyAnimal
{ /* ... */ };

TeddyBear 的虚基类的构造次序是先 ZooAnimal 再 ToyAnimal。一旦构造了虚基类,就按声明次序调用非虚基类的构造函数:首先是 BookCharacter,它导致调用 Character 构造函数,然后是 Bear。因此,为了创建 TeddyBear 对象,按下面次序调用构造函数:

ZooAnimal(); // Bear‘s virtual base class
ToyAnimal(); // immediate virtual base class
Character(); // BookCharacter‘s nonvirtual base class
BookCharacter(); // immediate nonvirtual base class
Bear(); // immediate nonvirtual base class
TeddyBear(); // most derived class

在这里,由最低层派生类 TeddyBear 指定用于 ZooAnimal 和 ToyAnimal 的初始化式。

在合成复制构造函数中使用同样的构造次序,在合成赋值操作符中也是按这个次序给基类赋值。保证调用基类析构函数的次序与构造函数的调用次序相反。

时间: 2024-11-10 07:09:11

C++多重继承的相关文章

016: class and objects > 多重继承与多态的例子

房屋代理模型: 1. Property class Property(object): def __init__(self, square_feet='', num_bedrooms='', num_baths='', **kwargs): super().__init__(**kwargs) self.square_feet = square_feet self.num_bedrooms = num_bedrooms self.num_baths = num_baths def display

C++多重继承中构造函数和析构函数调用顺序举例

//多重继承 #include <iostream> using namespace std; class A { public:     A()     {         cout<<"A基类构造A::A()"<<endl;     }     ~A()     {         cout<<"A基类析构A::~A()"<<endl;     } }; class B:public A { publi

C++多重继承关系举例

//多重继承 #include <iostream> using namespace std; class A { public:     int a;     A(int a=0):a(a)     {         cout<<"A基类A::A()"<<endl;     }     ~A()     {         cout<<"A基类A::~A()"<<endl;     }     void

C++多重继承中的虚继承和虚函数举例

上一篇虚继承举例:http://10638473.blog.51cto.com/10628473/1964414 本文将A类中的show()函数前加上virtual关键字. //多重继承 #include <iostream> using namespace std; class A { public:     int a;     A(int a=0):a(a)     {         cout<<"A基类A::A()"<<endl;     

Java提高篇——Java实现多重继承

阅读目录 一. 接口二.内部类 多重继承指的是一个类可以同时从多于一个的父类那里继承行为和特征,然而我们知道Java为了保证数据安全,它只允许单继承.有些时候我们会认为如果系统中需要使用多重继承往往都是糟糕的设计,这个时候我们往往需要思考的不是怎么使用多重继承,而是您的设计是否存在问题.但有时候我们确实是需要实现多重继承,而且现实生活中也真正地存在这样的情况,比如遗传:我们即继承了父亲的行为和特征也继承了母亲的行为和特征.可幸的是Java是非常和善和理解我们的,它提供了两种方式让我们曲折来实现多

JS---原型继承和多重继承

概念: 1.原型继承是创建新类型对象----子类型,子类型基于父类型,子类型拥有父类型所有的属性和方法(从父类型继承得到),然后修改其中的部分内容或者添加新的内容.继承最好在子类型模型可以被视为父类型对象的时候使用. 2.从多个父类型中派生出一个对象类型称为多重继承. 一.原型继承 使用new关键字和构造函数的prototype属性都是定义类型的特定方式,这些是我们到目前为止一直使用的,对于简单的对象,这种方式还是很好的,但是当程序过度使用继承时,这种创建对象的方法很快就显得笨拙了.所以增加一些

多重继承,虚继承,MI继承中虚继承中构造函数的调用情况

先来测试一些普通的多重继承.其实这个是显而易见的. 测试代码: [cpp] view plain copy print? //测试多重继承中派生类的构造函数的调用顺序何时调用 //Fedora20 gcc version=4.8.2 #include <iostream> using namespace std; class base { public: base() { cout<<"base created!"<<endl; } ~base()

第53课 被遗弃的多重继承(上)

1. 单一继承 (1)实验代码 #include <iostream> #include <string> using namespace std; void visitVtbl(int **vtbl) { cout << vtbl << endl; cout << "\t[-1]: " << (long)vtbl[-1] << endl; typedef void (*FuncPtr)(); int

C++之多重继承

大多数应用程序使用单个基类的公用继承,但是在某些情况下,单继承是不够的,必须使用多继承.C++允许为一个派生类指定多个基类,这样的继承结构被称做多重继承. 举个例子,交通工具类可以派生出汽车和船连个子类,但拥有汽车和船共同特性水陆两用汽车就必须继承来自汽车类与船类的共同属性.如下图示: 代码实现: //多重继承 #include <iostream> using namespace std; class Vehicle { public: Vehicle(int weight = 0) { V

php通过接口实现多重继承

php是单重继承的.一个类只有一个父类. 但是可以通过接口实现多重继承. 定义了一个接口,接口中有方法,假如接口给类去implements了,那么那个类需要有接口的方法.就像下面的代码 <?php interface d{ function b(); } class a implements d{ function b(){} } ?> 但是,如果a的类里面没有function b,就会报错 Fatal error: Class a contains 1 abstract method and