第十课 异常
- 错误
- 语法错误:编译系统
- 逻辑错误:测试
- 异常 Exception
- 运行环境造成:内存不足、文件操作失败等
- 异常处理:错误提示信息等
异常处理
特征:
- 可以预见
- 无法避免
作用:提高程序鲁棒性(Bobustness)
1
2
3
4
5
6
7
8void f(char *str) {//str可能是用户的一个输入
ifstream file(str);
if (file.fail()) {
// 异常处理
}
int x;
file >> x;
}问题:发现异常之处与处理异常之处不一致,怎么处理?函数中的异常要告知调用者
常见处理方式:
- 函数参数:
- 返回值(特殊的,0或者1)
- 引用参数(存放一些特定的信息)
- 逐层返回
- 函数参数:
缺陷:
- 程序结构不清楚 什么时候是异常的返回值、什么时候是正常的返回值
- 相同的异常,不同的地方,需要编写相同的处理了逻辑是不合理的
- 希望对异常进行集中的处理
传统异常处理方式不能处理构造函数出现的异常 需要创建资源,会出现异常,但是没有返回值
异常处理机制
- C++异常处理机制是,一种专门、清晰描述异常处理过程的机制
- try:监控
- throw:抛掷异常对象,不处理
- catch:捕获并处理
关键点:
(1)throw是将抛出的表达式的值拷贝到“异常对象”中,catch则是根据异常对象进行参数匹配并处理异常;
(2)throw可一次性跳出多层函数调用,直到最近一层的try语句,称为“栈展开”;
(3)catch捕获时是将异常对象与catch参数的进行** 类型比较,而不是值比较**,所以只要类型相同,就可以进入catch中处理。(例如throw抛出一个int类型的值,catch(int &i)就可以对其进行处理;或者throw抛出一个类对象,catch(Base& b)也可成功匹配)
所谓 “try”,就是 “尝试着执行一下”,如果有异常,则通过throw向外抛出,随后在外部通过catch捕获并处理异常。
1 | try{ |
- 不要抛出指出局部对象的引用或者指针,直接抛出一个对象

catch试图精确匹配,允许从非常量到常量的转换、从派生类到基类的转换、从数组和函数到指针的转换,但不能匹配到**int**转**double**。
throw的处理过程:(栈展开)
throw语句一般位于try语句块内,当throw抛出一个异常时,程序暂停当前函数的执行过程,并寻找与try语句块关联的catch语句(类似 switch…case…),
如果这一步没找到匹配的catch,且这一层的try语句外部又包含着另一层try,则在外层try中继续寻找匹配的catch,如果找不到,则退出当前函数,在当前函数的外层函数中继续寻找匹配的try与catch。
上述过程被称为“栈展开”(stack unwinding)过程。
栈展开 过程沿着嵌套函数的调用链不断查找,直到找到匹配的catch 子句为止;
或者一直没有找到匹配的catch,则退出主函数终止查找过程(调用标准库函数terminate)。
如果找到了一个匹配的catch子句,则程序进入该子句并执行其中的代码,执行完成后回到到这个 try…catch… 的最后一个catch之后的位置继续向下执行。
异常对象
在编译器的管理空间中,会维护一种“异常对象”,专门用于抛出异常时使用。
当发生异常时,编译器会用throw 抛出的表达式的值 对 “异常对象” 进行拷贝初始化,当异常处理完毕后,编译器会将“异常对象”销毁。
所以,基于 异常对象 的这种处理机制,对抛出异常的处理有几点限制:
① 如果throw抛出的表达式是类类型,则此类必须要有可访问的拷贝构造函数和析构函数;(因为对 异常对象 进行拷贝初始化 以及 释放 异常对象的时候需要调用)
② throw抛出的异常对象不能是指向局部对象的指针;(因为throw退出作用域后,局部对象随之被释放掉,抛出指针到外层后将无法访问所指向的局部对象)
③ throw抛出的表达式为此表达式的静态编译类型,如果抛出的是一个指向类对象的基类指针,则派生类部分将被截断,只有基类部分被抛出。
析构函数与异常:
当异常发生调用throw,后面的语句将不会被执行,退出作用域时,作用域的局部对象都将会被释放,对于类对象,退出作用域时将自动调用它的析构函数。
因此,如果析构函数中有抛出异常的流程,应该要在析构函数内部try捕获,并在析构函数内部得到处理。
初始化列表与异常
1 | Bob::Bob(string i1) try : data(i1) { |
函数参数与异常
通过对函数调用进行处理
构造函数与异常
参数部分:通过对new进行处理
异常处理嵌套
1 | f(){ |
catch块排列顺序
1 | class FileErrors { }; |
Catch exceptions by reference不使用引用,会发生对象的拷贝。使用引用,也可以直接对该对象处理,而不用对临时对象进行操作
例题??
1 | class MyExceptionBase {}; |

catch(...):捕获所有异常
多出口引发的处理碎片
一个语句块,可以以throw、return作为出口,因此会导致有多个出口
Java:finally:用来处理多出口,最后都会执行,用来释放资源。c++中没有finally- 异常处理器
- 析构函数
raii:将资源初始化为对象,由析构函数进行资源清理。即使是多出口,也可以通过析构函数进行处理。