异常处理 Exception Handling
Error Handling( 错误处理 ) 看似正确的程序在特殊情况下可能出错 如两个整数相加的程序, 用户输入的是字符串, 如果程序没有考虑到该情况, 则会出错! 可以在各种极端情况下都能正确应对的程序是健壮的 (Robust) 大型程序通常是分层的, 或者是模块化的 有底层的库 (API 函数 ) 调用, 有高层的用户接口 不同层次的模块发现异常情况后该如何处理?
Error Handling( 错误处理 ) 来看一种普通的航空订票系统 : 它的最高端是由一些 GUI 组件所组成, 用来在用户的屏幕上显示内容并与用户交互 这些高端组件与那些封装了数据库 API 的数据存取对象相互作用 再往底层一些, 那些数据库 API 与数据库引擎相交互, 然而数据库引擎自己又会调用系统服务来处理底层的硬件资源, 比如物理内存, 文件系统和安全模型
Error Handling( 错误处理 ) 一般情况下, 极其严格的运行期错误会在这些底层代码中被检测出来, 但是它们不能 ----- 或者说不应该 ---- 试图自己处理这些错误 解决这些严格的运行期错误的责任应该由高端组件来承担 为了解决一个错误, 高端组件必须得到错误发生的通知 本质上, 错误处理包括错误检测和通知高端组件 这些高端组件依次处理错误并且试图从错误中恢复
Error Handling- 抛出和捕获 库的作者可以检查出运行时错误, 但一般说根本不知道如何处理 ; 库的用户知道怎样处理错误, 但却无法去检查它们. 如 fopen() 函数知道文件是否正常打开了, 但用户不知道哪些文件可以正常打开, 用户要依赖 fopen() 去发现打开文件错误. 一个函数在发现自己无法处理的错误时, 可以抛出 (throw) 一个异常, 希望它的高层调用者能够处理, 调用者可以捕获 (catch) 自己能够处理的错误 ---- 这就是 C++ 异常处理的过程.
传统处理错误技术 [1] terminate the program/ 终止程序 [2] return a value representing error, / 返回表示错误的值 [3] return a legal value and leave the program in an illegal state / 返回合法值, 但使程序处于错误状态 ( 如设置一个全局错误变量 ) [4] call a function supplied to be called in case of error. / 当错误发生时, 调用预定的错误处理程序
[1] 终止程序 -exit() 和 abort() 在标准 C 的函数库中有两个函数用来终止一个程序 :exit() 和 abort() exit() 首先会清空流和关闭打开的文件 abort() 却不一样, 它表示程序被意外终止, 不会清空流和关闭打开的文件 是异常未被捕获情况下缺省发生的事情. 是一种最残酷的方法 突然终止可能使一些资源不能得到正确的释放, 数据未及时保存 如股票交易等
[2] 返回表示错误的值 错误码很难统一 因为一个库的实现者可能选择返回值 0 来代表一个错误, 然而另一个实现者却选择 0 来代表成功并且用那些非 0 值代表出现错误 微软从来就没有一个统一的错误码查询表, 不可能做到 对于每个调用都要检查返回值, 可使代码倍增. 有时根本不能返回值 如构造函数
[3] 使程序处于错误状态 同 [2] 有相同的问题, 而且调用者有时可能未意识到程序已处于非法状态.( 如 C 的 <errno.h> 中定义了一个全局变量 errno) 在一个多线程环境中, 被一个线程赋予了一个错误码的 errno 有可能不经意的被另一个线程所改写, 而调用者还未对 errno 进行检查
[4] 当错误发生时, 调用预定的错误处理程序 如果缺少关于异常情况的信息, 该错误处理程序仍然无能为力, 也只能采用类似前三种的方法.
异常处理机制 -- 更规范的错误处理技术 异常处理是一种把控制权从异常发生的地点转移到一个匹配的 handler 的机制 它将发现错误和处理错误的部分相互分离, 即将错误处理代码从 正常 代码中分离出来, 使程序更易阅读. 异常对象可以是内在类型变量或用户定义类型的对象, 可提供更多信息, 异常处理使系统从错误中恢复过来, 提高了程序的容错能力. 异常处理机制由四部分 : try 块, 一个或多个和 try 块相关的处理器 handler ( catch 块 ),throw 语句, 以及异常对象自己
异常处理 异常处理的默认相应方式是终止程序, 而传统的程序可以 装糊涂 继续运行. 异常处理可以处理同步异常, 如数组范围检查和 I/O 出错, 不能处理异步异常. 单击鼠标 ( 交互行为 ) 网络消息达到 I/O 完成等 异常处理机制是基于堆栈回退的非局部控制机制, 可以完成局部变量的销毁工作 如果程序的某些部分出现无法处理的情况, 可通知高层的环境去处理它以便从这个 异常 中恢复过来 异常多用于程序不同组件之间
#include <iostream.h> int fun(){ char *buf; try { buf = new char[512]; 异常处理 - 示例 if( buf == 0 ) throw "Memory allocation failure!" ; catch( char * str ) { cout << "Exception raised: " << str << '\n'; //... return 0;
try { 异常处理 - 示例 // Statements that process personnel data and may throw // exceptions of type int, string, and SalaryError catch ( int ) { // Statements to handle an int exception catch ( string s ) { cout << s << endl; // Prints "Invalid customer age" // More statements to handle an age error catch ( SalaryError ) { // Statements to handle a salary error
Execution of try-catch A statement throws an exception Control moves directly to exception handler Exception Handler No statements throw an exception Statements to deal with exception are executed Statement following entire try-catch statement
异常处理 - 示例 void Func3() { try { Function call Func4(); catch ( ErrType ) { Normal return void Func4() { if ( error ) throw ErrType(); Return from thrown exception
Practice: Dividing by ZERO int Quotient(int numer, { int denom ) if (denom!= 0) else return numer / denom; // The numerator // The denominator //What to do?? do sth. to avoid program //crash
A Solution int Quotient(int numer, // The numerator int denom ) // The denominator { if (denom == 0) throw DivByZero(); //throw exception of class DivByZero return numer / denom;
A Solution // quotient.cpp -- Quotient program #include<iostream.h> #include <string.h> int Quotient( int, int ); class DivByZero {; // Exception class int main() { int numer; // Numerator int denom; // Denominator //read in numerator and denominator while(cin){ try { cout << "Their quotient: " << Quotient(numer,denom) <<endl; catch ( DivByZero )//exception handler { cout<< Denominator can't be 0"<< endl; // read in numerator and denominator return 0;
异常四要素 异常对象 : 可以是任何类型, 但一般不会用 int string 等, 而是用一个用户定义对象表示异常 因为要使它们好分辨, 同时包含更多异常信息 try 块放上要检查异常的代码 每个 catch 块指定捕获的异常和处理这些异常的异常处理器 throw 用来抛出异常对象或者说明抛出哪些异常对象
Stack Unwinding / 栈回退 当一个异常被抛出, 运行时机制首先在当前的作用域寻找合适的 handler 如果不存在这样一个 handler, 那么将会离开当前的作用域, 进入更外围的一层继续寻找 这个过程不断的进行下去直到合适的 handler 被找到为止 此时堆栈已经被解开, 并且所有的局部对象被销毁 如果始终都没有找到合适的 handler, 那么程序将会终止
捕获任何异常 注意,C++ 保证局部对象被适当的销毁仅仅是在抛出的异常被处理的情况下 一个未被扑获得异常是否引起局部对象的销毁由实现决定的 为了保证局部对象的析构函数在异常未被捕获情况下也能够被正常调用, 你应该在 main() 里加入捕获任何异常的 catch 语句
捕获任何异常 int main(){ try { //... catch(std::exception& stdexc) { //... catch(...) // { // return 0; // exception handler
stack unwinding- 类似函数调用返回 调用者 函数 f 捕获并处理异常 调用关系 异常回退 函数 g() 函数 h() 抛出异常
Grouping of Exceptions/ 异常的组织 一个异常就是某个表示异常类的对象 异常类型可以是任何类型, 但习惯上将它们组织成一定结构如层次结构 从而对异常可以分类 如某数学库的异常可能 : class Matherr { ; class Overflow: public Matherr { ; class Underflow: public Matherr { ; class Zerodivide: public Matherr { ; //...
异常的组织 可以处理所有的数学异常, 也可以处理特定的数学异常. void f(){ try { //... catch (Overflow) { // handle Overflow or anything derived from Overflow catch (Matherr) { // handle any Matherr that is not Overflow
某些算术错误未作为异常 对某些算术错误, 由于许多流水线系统结构中都是非同步的操作, 所以未作为异常 如除 0
Derived Exceptions/ 派生的异常 由于异常通常被组织成一个层次结构, 即异常类型之间是派生关系 并且捕获和命名异常的语义等同于函数接受参数的语义, 即用实际参数对形式参数初始化 那么抛出的异常在被捕获时就会产生切割问题 解决方法与函数参数一样, 即传递异常对象的指针或引用
void g() throw Overflow { // throw Overflow(); void f(){ try { g() ; catch (Matherr m) { //... 异常对象被切割
异常对象被切割 int add(int x, int y){ if ((x>0 && y>0 && x>int_maxy) (x<0 && y<0 && x<int_miny)) throw Int_overflow("+,x, y) ; return x+y; // x+y will not overflow void f(){ try { int i1 = add(1,2) ; int i2 = add(int_max, 2); int i3 = add(int_max,2); // here we go! catch (Matherr& m) { //... m.debug_print() ;