c++ primer 第十八章用于大型程序的工具

18.1 异常处理

异常处理机制允许程序中独立开发的部分能够在运行时对出现的问题进行通信并进行相应处理。使得问题的检测和解决过程分离。

18.1.1 抛出异常

抛出一条表达式来引发一个异常。匹配的处理代码是调用链中与抛出对象类型匹配的最近的处理代码。

throw后面的代码不再执行,控制权转移到对应的catch代码。因此:1.调用链中的函数可能提前退出。2.执行异常处理代码时调用链中创建的对象会被销毁。

try catch匹配时的顺序规则,称为栈展开。找到匹配的catch语句为止,没找到则程序终止。

在栈展开的过程中退出了块,编译器负责确保块中创建的对象能被正确销毁。

析构函数需要释放块中分配的资源,因此不应该提前结束,因此不能抛出不能被自身处理的异常。

异常对象是一种特殊对象,抛出表达式对其进行拷贝初始化,因此必须拥有完全类型。

抛出表达式时,表达式的静态编译时类型决定了其类型。基类指针指向派生类的抛出对象会被切掉一部分。

18.1.2 捕获异常

catch子句的异常声明像是只包含一个形参的函数形参列表。声明类型决定捕捉的异常类型。可以是左值引用。

静态类型决定异常对象的操作,非引用类型对象会被切到与静态声明类型相符。

catch匹配顺序是从前往后依次匹配,允许非常量向常量的转换,派生类到基类的转换以及数组和函数到指针的转换。

catch语句中进行一定的处理之后可以重新抛出异常给更上层语句,此时抛出语句是throw;。抛出的是原来的异常对象。

catch(…)可以捕获所有类型的异常,因此一般放在最后一个或者单独出现。

18.1.3 函数try语句块与构造函数

处理构造函数中抛出的异常需要将try catch语句和构造函数写在一起。

18.1.4 noexcept异常说明

使用noexcept符号可以设定函数不抛出异常。加在const和引用说明符后面,在final、override或虚函数=0之前。

标明noexcept却抛出异常的函数会导致程序直接terminate。

noexcept也可以接受一个bool实参,true表示不抛出异常。

noexcept也可以当做表达式,参数是函数。当函数调用的方法都不抛出异常为真。

函数指针与其所指函数有一致的异常说明。

虚函数不抛出异常那么派生的函数也不抛出。如果虚函数允许那么派生类对应函数都可以。

编译器合成拷贝控制成员时也生成一个异常说明,根据所有成员和基类操作而定。

18.1.5 异常类层次

exception类定义了拷贝构造函数,拷贝构造运算符,析构函数和一个名为what的虚函数成员。

exception派生出bad_cast、bad_alloc、runtime_error和logic_error四种类型的异常。

可以继承基本类型的异常使用我们自己的异常类型。

18.2 命名空间

命名空间防止名字冲突分割了全局命名空间,每个命名空间是一个作用域。

18.2.1 命名空间定义

命名空间namespace加名字和{}作为一个作用域,里面可以包括所有可以放在全局作用域中的声明。

命名空间可以定义在全局作用域中,也可以在其它命名空间中。但不能在函数或类的内部。

每个命名空间都是一个作用域,不同命名空间可以拥有相同名字的成员。

命名空间可以是不连续的。因此可以帮助接口和实现的分离。

一般不把#include放在命名空间内部。因为会把头文件中的所有名字作为该命名空间的成员。

可以在命名空间的外部定义该空间的成员。但是只能在该命名空间的外层空间中定义。

全局命名空间使用::表示来调用。

命名空间可以嵌套,使用时需要全部写出来。

内联命名空间在namespace前加inline。inline命名空间中的名字可以被外层命名空间直接使用。

未命名的命名空间namespace{}。其中的变量拥有静态生命周期。在新标准中替代全局static。

未命名的命名空间可以不连续但是不能跨越文件。跨文件的未命名的命名空间是不同的作用域。

18.2.2 使用命名空间成员

命名空间别名 namespace primer = cplusplus_primer;等,也可以等于一个嵌套的命名空间。

using声明。声明命名空间中的一个成员。从声明的地方开始,到所在作用域结束为止。在此期间外层作用域名字被隐藏。在类作用域中using声明只能作用域基类成员。

using指示 using namespace std; 直接将命名空间中所有名字都可见。但不能用在类作用域中。

using指示作用域较为复杂。一般using指示被看做是出现在最近的外层作用域中。使用using指示时允许出现名字冲突,但调用时若未加限定则出现二义性错误。

头文件顶层作用域包含using指示时,会将该命名空间注入到所有包含该头文件的文件内。因此一般在头文件中最多只能在函数或命名空间中使用using指示或using声明。

18.2.3 类、命名空间和作用域

命名空间的名字查找与普通作用域相同。命名空间中的类成员现在成员中查找,然后在类中(基类中)查找,之后到外层空间。

命名空间的名字隐藏规则有个例外:当我们给函数传递一个类类型的对象时,除了常规作用域查找,还会查找实参类所属的命名空间。

使用std::move和std::forward需要指明命名空间避免冲突。

友元声明会被隐式当做是其最近的外层命名空间的成员。与隐藏规则相互作用。

18.2.4 重载与命名空间

命名空间会对函数匹配过程有两方面影响,一个是会使得某些函数加入候选。

实参是类类型时会影响函数匹配的查找过程。

using声明的是一个名字,声明一个函数时会引入所有版本。

using声明引入的函数会重载当前所属作用域的同名函数。using声明在局部作用域时会隐藏外部名字。如果所属作用域有完全相同声明的函数会引起错误。

使用using指示将空间中的名字引入外层作用域中,若同名则加入重载。如果完全相同也不会出错。但是使用时需要指明版本。

多个using指示的同名函数都会加入重载。

18.3 多重继承与虚继承

多重继承指的是从多个直接基类中产生派生类的能力。

18.3.1 多重继承

派生列表中可以有多个基类,每个类都有一个访问说明符。

多重继承中派生类的对象包含每个基类的子对象。

构造一个派生类的对象将同时构造并初始化它的所有基类子对象,构造顺序与派生列表的出现顺序一致。

派生类可以从一个或多个基类中继承构造函数。如果从多个基类中继承了相同的构造函数(形参列表完全相同)则程序产生错误。因此需要定义新的构造函数。

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

多重继承的派生类如果定义了自己的拷贝/赋值构造函数和赋值运算符,必须在完整的对象上进行。

18.3.2 类型转换与多个基类

多重继承的派生类成员可以转换到任意一个基类的指针或引用。编译器认为它们一样好。

一个指针或引用的静态类型决定了它能够使用的成员。

18.3.3 多重继承下的类作用域

多重继承下,相同的查找过程在所有基类中同时进行。若名字在多个基类中被找到,引发二义性。

继承不同基类中的同名成员是合法的,但是使用时需要指定。

因为编译器先查找名字后进行类型检查,因此在派生类继承的两个参数形参列表不同也可能发生错误等等。

18.3.4 虚继承

实际上派生类可以多次继承同一个类,不过是不同的直接基类。

如果某个类在派生过程中出现了多次,派生类中将包含该类的多个子对象。

虚继承可以使得继承体系中的某个基类进行共享,派生类中只含一个该类的对象。

在派生类列表对应类前加关键字virtual声明虚基类。

虚基类的成员可以直接调用,不存在二义性。

18.3.5 构造函数与虚继承

虚基类需要由最低层的派生类初始化。

含有虚基类的对象的构造顺序为:首先使用提供给最低层派生类构造函数的初始值初始化该对象虚基类子部分,接着按照直接基类在派生列表中出现的次序进行初始化。

析构函数虚构造顺序相反。

猜你喜欢

转载自blog.csdn.net/qq_25037903/article/details/83542540
今日推荐