大型程序的工具——多重继承与虚继承

一、多重继承与虚继承

1、多重继承

  在派生类的派生列表中可以包含多个基类,每个基类包含一个可选的访问说明符。

  多重继承的派生列表也只能包含已经定义过的类,而且这些类不能是final的。对于派生类能够继承的基类个数,C++没有进行特殊规定;但是在某个给定的派生列表中,同一个基类只能出现一次。

1)多重继承的派生类从每个基类中继承状态

  在多重继承关系中,派生类的对象包含有每个基类的子对象。

2)派生类构造函数初始化所有基类

  构造一个派生类的对象将同时构造并初始化它的所有基类子对象。与从一个基类进行的派生一样,多重继承的派生类的构造函数初始值也只能初始化它的直接基类。

  派生类的构造函数将初始值列表将实参分别传递给每个直接基类。其中基类的构造顺序与派生列表中基类的出现顺序保持一致,而与派生类构造函数初始值列表中基类的顺序无关。

3)继承的构造函数与多重继承

  在C++11新标准中,允许派生类从它的一个或几个基类中继承构造函数。但是如果从多个基类中继承了相同的构造函数(即形参列表完全相同),则程序将产生错误。

  如果一个类从它的多个基类中继承了相同的构造函数,则这个类必须为该构造函数定义它自己的版本。

4)析构函数与多重继承

  派生类的析构函数只负责清除派生类本身分配的资源,派生类的成员及基类都是自动销毁的。

  析构函数的调用顺序正好与构造函数相反。

5)多重继承的派生类的拷贝与移动操作

  与只有一个基类的继承一样,多重继承的派生类如果定义了自己的拷贝/赋值构造函数和赋值运算符,则必须在完整的对象上执行拷贝、移动或赋值操作。只有当派生类使用的是合成版本的拷贝、移动或赋值成员时,才会自动对其基类分别执行这些操作。在合成的拷贝控制成员中,每个基类分别使用自己的对应成员隐式地完成构造、赋值或销毁等工作。

2、类型转换与多个基类

  在只有一个基类的情况下,派生类的指针或引用能自动转换成一个可访问基类的指针或引用。多个基类的情况与之类似。

  编译器不会再派生类向基类的几种转换中进行选择和比较,因为在它看来转换到任意一种基类都一样好。

1)基于指针类型或引用类型的查找

  与只有一个基类的继承一样,对象、指针和引用的静态类型决定了我们能够使用哪些成员。

3、多重继承下的类作用域

  在只有一个基类的情况下,派生类的作用域嵌套在直接基类和间接基类的作用域中。查找过程沿着继承体系自底向上进行,直到找到所需的名字。派生类的名字将隐藏基类的同名成员。

  在多重继承的情况下,相同的查找过程在所有直接基类中同时进行。如果名字在多个基类中都被找到,则对该名字的使用将具有二义性。

  对于一个派生类来说,从它的几个基类中分别继承名字相同的成员是完全合法的,只不过在使用这个名字时必须明确指出它的版本。

4、虚继承

  在默认情况下,派生类中含有继承链上每个类对应的子部分。如果某个类在派生过程中出现了多次,则派生类中将包含该类的多个子对象。

  在C++语言中我们通过虚继承的机制解决上述问题。虚继承的目的是令某个类做出声明,承诺愿意共享它的基类。其中,共享的基类子对象称为虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含唯一一个个共享的虚基类子对象。

1)使用虚基类

  我们指定虚基类的方式是在派生列表中添加关键字virtual。virtual说明符表明了一种愿望,即在后续的派生类当中共享虚基类的同一份实例 。如果某个类指定了虚基类,则该类的派生仍按常规方式进行。

 1 #include <iostream>
 2 #include <string>
 3
 4 class Base {
 5 public:
 6     Base() {
 7         std::cout << __FUNCTION__ << std::endl;
 8     }
 9 };
10 class D1 :public virtual Base {
11 public:
12     D1() :Base() {
13         std::cout << __FUNCTION__ << std::endl;
14     }
15 };
16 class D2 :public virtual Base {
17 public:
18     D2() :Base() {
19         std::cout << __FUNCTION__ << std::endl;
20     }
21 };
22 class D :public D1, public D2 {
23 public:
24     D() :D1(), D2() {
25         std::cout << __FUNCTION__ << std::endl;
26     }
27 };
28 int main()
29 {
30     D d;
31     return 0;
32 }

2)支持向基类的常规类型转换

  不论基类是不是虚基类,派生类对象都能被可访问基类的指针或引用操作。

3)虚基类成员的可见性

5、构造函数与虚继承

1)虚继承的对象的构造方式

  含有虚基类的对象的构造顺序与一般的顺序稍有区别:首先使用提供给最底层派生类构造函数的初始值初始化该对象的虚基类子部分,接下来按照直接基类在派生列表中出现的次序依次对其进行初始化。如果没有显式的初始化虚基类,则虚基类的默认构造函数被调用。

 1 #include <iostream>
 2 #include <string>
 3
 4 class Base {
 5 public:
 6     Base() {
 7         std::cout << __FUNCTION__ << std::endl;
 8     }
 9     Base(int x):bx(x) {
10         std::cout << "hello Base" << std::endl;
11     }
12 private:
13     int bx;
14 };
15 class D1 :public virtual Base {
16 public:
17     D1(int x) :Base(x), dx1(x) {
18         std::cout << __FUNCTION__ << std::endl;
19     }
20 private:
21     int dx1;
22 };
23 class D2 :public virtual Base {
24 public:
25     D2(int x) :Base(x), dx2(x) {
26         std::cout << __FUNCTION__ << std::endl;
27     }
28 private:
29     int dx2;
30 };
31 class D :public D1, public D2 {
32 public:
33     D(int x=0) :Base(x), D1(x), D2(x) {
34         std::cout << __FUNCTION__ << std::endl;
35     }
36 private:
37     int d;
38 };
39 int main()
40 {
41     D d;
42     return 0;
43 }

  虚基类总是先于非虚基类构造,与它们在继承体系中的次序和位置无关。

2)构造函数与析构函数的次序

  一个类可以有多个虚基类。此时,这些虚的子对象按照它们在派生列表中出现的顺序从左向右依次构造。

  合成的拷贝和移动构造函数按照完全相同的顺序执行,合成的赋值运算符中的成员也按照该顺序赋值。和往常一样,对象的销毁顺序与构造顺序正好相反。

原文地址:https://www.cnblogs.com/ACGame/p/10347502.html

时间: 2025-01-18 00:04:36

大型程序的工具——多重继承与虚继承的相关文章

C++ Primer 学习笔记_95_用于大型程序的工具 --多重继承与虚继承

用于大型程序的工具 --多重继承与虚继承 引言: 大多数应用程序使用单个基类的公用继承,但是,在某些情况下,单继承是不够用的,因为可能无法为问题域建模,或者会对模型带来不必要的复杂性. 在这些情况下,多重继承可以更直接地为应用程序建模.多重继承是从多于一个直接基类派生类的能力,多重继承的派生类继承其所有父类的属性. 一.多重继承 1.定义多个类 为了支持多重继承,扩充派生列表: class Bear : public ZooAnimal { //... }; 以支持由逗号分隔的基类列表: cla

C++ Primer 学习笔记_96_用于大型程序的工具 --多重继承与虚继承[续1]

用于大型程序的工具 --多重继承与虚继承[续1] 四.多重继承下的类作用域 成员函数中使用的名字和查找首先在函数本身进行,如果不能在本地找到名字,就继续在本类中查找,然后依次查找每个基类.在多重继承下,查找同时检察所有的基类继承子树 -- 在我们的例子中,并行查找 Endangered子树和Bear/ZooAnimal子树.如果在多个子树中找到该名字,则那个名字的使用必须显式指定使用哪个基类;否则,该名字的使用是二义性的. [小心地雷] 当一个类有多个基类的时候,通过对所有直接基类同时进行名字查

C++ Primer 学习笔记_97_用于大型程序的工具 --多重继承与虚继承[续2]

用于大型程序的工具 --多重继承与虚继承[续2] 七.特殊的初始化语义 从具有虚基类的类继承的类对初始化进行特殊处理:在虚基类中,由最低层派生类的构造函数初始化虚基类.在ZooAnimal示例中,使用常规规则将导致Bear 类和 Raccoon类都试图初始化Panda对象的ZooAnimal类部分. 虽然由最低层派生类初始化虚基类,但是任何直接或间接继承虚基类的类一般也必须为该基类提供自己的初始化式.只要可以创建虚基类派生类类型的独立对象,该类就必须初始化自己的虚基类,这些初始化只在创建中间类型

C++ Primer 学习笔记_87_用于大型程序的工具 --异常处理

用于大型程序的工具 --异常处理 引言: C++语言包括的一些特征在问题比較复杂,非个人所能管理时最为实用.如:异常处理.命名空间和多重继承. 相对于小的程序猿团队所能开发的系统需求而言,大规模编程[往往涉及数千万行代码]对程序设计语言的要求更高.大规模应用程序往往具有下列特殊要求: 1.更严格的正常运转时间以及更健壮的错误检測和错误处理.错误处理常常必须跨越独立开发的多个子系统进行[异常处理]. 2.能够用各种库(可能包括独立开发的库)构造程序[命名空间]. 3.能够处理更复杂的应用概念[多重

【C++ Primer】用于大型程序的工具

1. 异常处理 异常以类似于将实参传递给函数的方式抛出和捕获.异常可以是可传给非引用实参的任意实参的类型,这意味着必须能够复制该类型的对象. 当抛出一个表达式的时候,被抛出对象的静态编译时类型将决定异常对象的类型. 抛出指针通常是个坏主意. 栈展开:沿着嵌套函数调用链继续向上,直到为异常找到一个catch子句. 栈展开期间,释放局部对象所用的内存并运行类类型局部对象的析构函数. 一个块可以通过new动态分配内存,如果该块因异常而退出,编译器不会删除该指针,已分配的内存将不会释放. 析构函数应该从

C++ Primer 学习笔记_87_用于大型程序的工具 -错误处理

用于大型程序的工具 --异常处理 引言: C++语言包含的一些特征在问题比较复杂,非个人所能管理时最为有用.如:异常处理.命名空间和多重继承. 相对于小的程序员团队所能开发的系统需求而言,大规模编程[往往涉及数千万行代码]对程序设计语言的要求更高.大规模应用程序往往具有下列特殊要求: 1.更严格的正常运转时间以及更健壮的错误检测和错误处理.错误处理经常必须跨越独立开发的多个子系统进行[异常处理]. 2.能够用各种库(可能包含独立开发的库)构造程序[命名空间]. 3.能够处理更复杂的应用概念[多重

C++ Primer 学习笔记_90_用于大型程序的工具 --异常处理[续3]

用于大型程序的工具 --异常处理[续3] 九.auto_ptr类[接上] 5.auto_ptr对象的复制和赋值是破坏性操作 auto_ptr和内置指针对待复制和赋值有非常关键的区别.当复制auto_ptr对象或者将它的值赋给其他auto_ptr对象的时候,将基础对象的所有权从原来的auto_ptr对象转给副本,原来的auto_ptr对象重置为未绑定状态. auto_ptr<string> strPtr1(new string("HELLO!")); auto_ptr<

C++ Primer 学习笔记_90_用于大型程序的工具 -错误处理[续3]

用于大型程序的工具 --异常处理[续3] 九.auto_ptr类[接上] 5.auto_ptr对象的复制和赋值是破坏性操作 auto_ptr和内置指针对待复制和赋值有非常关键的区别.当复制auto_ptr对象或者将它的值赋给其他auto_ptr对象的时候,将基础对象的所有权从原来的auto_ptr对象转给副本,原来的auto_ptr对象重置为未绑定状态. auto_ptr<string> strPtr1(new string("HELLO!")); auto_ptr<

第18章 用于大型程序的工具

18.1异常处理 try { // actions that cause an exception to be thrown } catch (...) { // work to partially handle the exception throw; } 在C++中,通过throwing来raised一个exception.当throw时,throw后边的语句不再执行,转移到catch中,这意味: 沿着调用链的函数可能会提早退出 一点开始执行异常处理代码,沿着调用链创建的对象将被销毁 当th