C++ Primer系列 第18章 用于大型程序的工具
与仅虚几个程序员就能开发完成的系统相比,大规模编程对程序设计语言的要求更高。大规模应用程序的特殊要求包括:
- 在独立开发的子系统之间协同处理错误的能力。
- 使用各种库(可能包含独立开发的库)进行协同开发的能力。
- 对比较复杂的应用概念建模的能力。
本章介绍的三种C++语言特征正好能满足上述要求,它们是:异常处理,命名空间和多重继承。
18.1 异常处理
异常处理(exception handling)机制允许程序中独立开发的部分能够在运行时就出现的问题进行通信并作出相应的处理。
18.1.1 抛出异常
当执行一个throw时,跟在throw后面的语句将不再被执行。相反,程序的控制权从throw转移到与之匹配的catch模块。该catch可能是同一函数中的局部catch,也可能是位于直接或间接调用了发生异常的函数的另一个函数中。控制权从一处转移到另一处,这有两个重要的含义:
- 沿着调用链的函数可能会提早退出。
- 一旦程序开始执行异常处理代码,则沿着调用链创建的对象将会被销毁。
- 一个异常如果没有被捕获,则它将终止当前的程序。
- 在栈展开的过程中,运行类类型的局部对象的析构函数。因为这些析构函数是自动执行的,所以它们不应该抛出异常。一旦在栈展开的过程中析构函数抛出了异常,并且析构函数自身没能捕获到该异常,则程序将被终止。
- 抛出指针要求在任何对应的处理代码存在的地方,指针所指的对象都必须存在。
18.1.2 捕获异常
异常声明的静态类型将决定catch语句所能执行的操作。如果catch的参数是基类类型,则catch无法使用派生类特有的任何成员。
- 通常情况下,如果catch接受的异常与某个继承体系有关,则最好将该catch的参数定义成引用类型。
在搜寻catch语句的过程中,我们最终找到的catch未必是异常的最佳匹配。相反,挑选出来的应该是第一个与异常匹配的catch语句。因此,越是专门的catch越应该置于整个catch列表的前端。
- 如果在多个catch语句的类型之间存在着继承关系,则我们应该把继承链最底端的类(most derived type)放在前面,而将继承链最顶端的类(least derived type)放在后面。
重新抛出
catch (my_error& eObj) {
// 引用类型
eObj.status = errCodes::serverErr; // 修改了异常对象
throw; // 异常对象的status成员是serverErr
} catch (other_error eObj) {
// 非引用类型
eObj.status = errCodes::serverErr; // 只修改了异常对象的局部副本
throw; // 异常对象的status成员没有改变
}
捕获所有异常的处理代码
void mainip() {
try {
// 这里的操作将引发并抛出一个异常
} catch (...) {
// 处理异常的某些特殊操作
throw;
}
}
18.1.3 函数try语句块与构造函数
要想处理构造函数初始值抛出的异常,我们必须将构造函数写成函数try语句块(也成为函数测试块,function try block)的形式。
template <typename T>
Blob<T>::Blob(std::initializer_list<T> il) try :
data(std::make_shared<std::vector<T>>(il)) {
/*空函数体*/
} catch (const std