Download presentation
Presentation is loading. Please wait.
Published byΖώνα Ξένη Παπανικολάου Modified 5年之前
1
刘胥影 liuxy@seu.edu.cn 东南大学计算机学院
面向对象程序设计1 2010~2011第3学期 刘胥影 东南大学计算机学院
2
Exception Handling 教学要求与安排
全部要求 东南大学计算机学院 1/17/2019
3
Exception Handling 16.1 异常处理简介 16.2 深入了解异常
异常对象 标准异常类 异常匹配 重新抛出异常 异常规格 (chap16.1, 16.2, , 16.13) (chap16.13) (chap16.10, 16.13) (chap16.5) (chap16.6, 16.7)
4
Exception Handling 16.3 几类异常处理 (chap16.14) 16.4 其他异常处理技术 栈展开
构造函数、析构函数和异常处理 动态内存分配的异常处理 16.4 其他异常处理技术 (chap16.8) (chap16.9) (chap16.11, chap16.12) (chap16.14)
5
异常处理简介 异常 异常处理 实例:处理除数为0的异常 异常处理的用处
6
异常 异常(Exception) 异常指示了程序在运行时出现了问题 e.g. 除数为0 int a = 1; int b = 0;
cout<<a/b; 有未经处理的异常: Integer division by zero 东南大学计算机学院 1/17/2019
7
异常 常见的异常类型 除数为0 运算溢出 数组下标越界 无效的参数 内存分配失败 … … 东南大学计算机学院 1/17/2019
8
dynamic_cast运算符抛出的异常
标准异常 意料之外的异常 异常 仅在运行时才能检查到 运行前能检查到 运行时错误 逻辑错误 运算上溢错误 运算下溢错误 非法参数传递错误 长度超限错误 下标越界错误 运算符抛出的异常 new运算符抛出的异常 dynamic_cast运算符抛出的异常 typeid运算符抛出的异常 东南大学计算机学院 1/17/2019
9
异常处理 异常处理的步骤 抛出异常 侦测异常 处理异常 特别注意:抛出点必须在可能出错的语句之前 异常处理模式
:throw 抛出点 (throw point) :try :catch 异常处理模式 try { } 每个try块后面至少立即跟着一个catch处理器 可能出现异常的语句块 throw 可能出错的语句; 特定类型的异常; 定义异常类,描述问题, 在throw和catch间传递信息 继承标准库异常类 自定义 catch(特定类型的异常) { 处理异常; } 异常参数列表 catch(异常类类型 异常参数) 东南大学计算机学院 1/17/2019
10
异常处理 抛出多个异常 异常处理模式 每个try块后面至少立即跟着一个catch处理器 可能出现异常的语句块
{ } catch(异常1) 处理异常1; 可能出现异常的语句块 throw 异常1; … … throw 异常2; catch(异常2) 处理异常2; 抛出多个异常 每个try块后面至少立即跟着一个catch处理器 catch处理器会匹配异常的类型 与抛出的异常类型相同 是抛出的异常的基类 东南大学计算机学院 1/17/2019
11
异常处理 异常类型匹配 抛出异常 的类型 catch处理器中 异常参数的类型 类型相同 抛出异常 的类型 catch处理器中 异常参数的类型
继承 基类的指针(引用)可以指向派生类的对象 东南大学计算机学院 1/17/2019
12
每个try块和相应的catch处理器间有代码是错误的
异常处理 try { … … throw 异常1; } 语句; //error catch(异常1) 处理异常1; try { … … throw 异常1; throw 异常2; } catch(异常1, 异常2) 处理异常1; 处理异常2; try { … … throw 异常1; throw 异常2; } catch(异常1) 处理异常1; 每个try块和相应的catch处理器间有代码是错误的 catch处理器只有一个参数(只能处理一种异常) 多个catch处理器捕获相同的异常是逻辑错误
13
异常处理 异常处理的终止模式 try语句块 catch处理器 throw 异常1 if … … throw 异常2 异常? 异常类型
true false … … throw 异常2 异常? 异常类型 catch(异常1){…} catch(异常2){…} 异常处理的终止模式 … … try { throw 异常1; throw 异常2; } catch(异常1) 处理异常1; catch(异常2) 处理异常2;
14
异常处理 若无异常 try语句块 catch处理器 throw 异常1 if … … throw 异常2 异常? 异常类型
true false … … throw 异常2 异常? 异常类型 catch(异常1){…} catch(异常2){…} 若无异常
15
异常处理 若抛出异常1 try语句块 catch处理器 throw 异常1 if … … throw 异常2 异常? 异常类型
true false … … throw 异常2 异常? 异常类型 catch(异常1){…} catch(异常2){…} 若抛出异常1
16
异常处理 若抛出异常2 try语句块 catch处理器 throw 异常1 if … … throw 异常2 异常? 异常类型
true false … … throw 异常2 异常? 异常类型 catch(异常1){…} catch(异常2){…} 若抛出异常2
17
dynamic_cast运算符抛出的异常
异常处理 回顾 标准异常类型 意料之外的异常 异常 运行时错误 逻辑错误 运算上溢出错误 运算下溢出错误 非法参数传递错误 长度超限错误 下标越界错误 运算符抛出的异常 new运算符抛出的异常 dynamic_cast运算符抛出的异常 typeid运算符抛出的异常 东南大学计算机学院 1/17/2019
18
dynamic_cast运算符抛出的异常
异常处理 标准库异常类层次 Exception bad_alloc bad_cast bad_type_id invalid_argument length_error out_of_range logic_error overflow_error underflow_error runtime_error bad_exception 运行时错误 逻辑错误 运算上溢错误 运算下溢错误 非法参数错误 长度超限错误 下标越界错误 new运算符抛出的异常 dynamic_cast运算符抛出的异常 typeid运算符抛出的异常 意料之外的异常 <exception> <stdexcept> <stdexcept> 东南大学计算机学院 1/17/2019
19
实例:处理除数为0的异常 double quotient(int numerator, int denominator){
if(denominator == 0) \\error,应抛出特定异常 return <static_cast>(numerator) / denominator; } main(){ int number1, number2; double result; cout << "Enter two integers (end-of-file to end): "; while(cin>>number1>>number2) { result = quotient(number1, number2); cout<< "The quotient is: " << result << endl; }
20
实例:处理除数为0的异常 异常处理第1步:抛出异常(throw)
double quotient(int numerator, int denominator){ if(denominator == 0) \\error,应抛出特定异常 return static_cast <double>(numerator) / denominator; } throw DivideByZeroException(); 若throw的操作数为对象,称为异常对象 #include <stdexcept> using std::runtime_error; class DivideByZeroException : public runtime_error { public: DivideByZeroException() : runtime_error( "attempted to divide by zero" ) {} }; Fig. 16.1: DivideByZeroException.h 说明问题类型
21
实例:处理除数为0的异常 异常处理第2步:侦测异常(try) main(){ int number1, number2;
double result; cout << "Enter two integers (end-of-file to end): "; while(cin>>number1>>number2) { result = quotient(number1, number2); cout<< "The quotient is: " << result << endl; } try { result = quotient(number1, number2); cout<< "The quotient is: " << result << endl; } 会出现异常的语句块 注意,不是对整个 while语句try 将cout语句包含进try,可保证quotient函数返回结果时才输出
22
实例:处理除数为0的异常 异常处理第3步:处理异常(catch) main(){ ……
while(cin>>number1>>number2) { result = quotient(number1, number2); cout<< "The quotient is: " << result << endl; cout << "Enter two integers (end-of-file to end): "; } try { result = quotient(number1, number2); cout<< "The quotient is: " << result << endl; } throw DivideByZeroException(); catch( DivideByZeroException ÷ByZeroException ) { cout << "Exception occurred: " << divideByZeroException.what() << endl; } exception类的virtual函数what(),返回异常的错误信息
23
实例:处理除数为0的异常 测试 Enter two integers (end-of-file to end): 100 7
The quotient is:
24
13 double quotient( int numerator, int denominator ) {
if ( denominator == 0 ) throw DivideByZeroException(); 18 return static_cast< double >( numerator ) / denominator; 21 } 23 int main(){ cout << "Enter two integers (end-of-file to end): "; while ( cin >> number1 >> number2 ) { try { result = quotient( number1, number2 ); cout << "The quotient is: " << result << endl; } catch ( DivideByZeroException ÷ByZeroException ) { cout << "Exception occurred: " << divideByZeroException.what() << endl; } cout << "\nEnter two integers (end-of-file to end): "; } cout << endl; 54 }
25
实例:处理除数为0的异常 测试 Enter two integers (end-of-file to end): 100 7
The quotient is: Enter two integers (end-of-file to end): 100 0 Exception occurred: attempted to divide by zero class DivideByZeroException : public runtime_error { public: DivideByZeroException() : runtime_error( "attempted to divide by zero" ) {} };
26
13 double quotient( int numerator, int denominator ) {
if ( denominator == 0 ) throw DivideByZeroException(); 18 return static_cast< double >( numerator ) / denominator; 21 } 23 int main(){ cout << "Enter two integers (end-of-file to end): "; while ( cin >> number1 >> number2 ) { try { result = quotient( number1, number2 ); cout << "The quotient is: " << result << endl; } catch ( DivideByZeroException ÷ByZeroException ) { cout << "Exception occurred: " << divideByZeroException.what() << endl; } cout << "\nEnter two integers (end-of-file to end): "; } cout << endl; 54 }
27
内容回顾 异常 异常处理的三个步骤 异常匹配 标准库异常类的继承层次 抛出异常:throw 侦测异常:try 处理异常:catch 类型相同
throw的异常时catch异常形参的派生类 标准库异常类的继承层次 东南大学计算机学院 1/17/2019
28
异常处理的用处 何时使用异常处理? 异常处理的好处? 只能处理同步错误,不能处理异步事件 程序能在解决问题后继续执行(而不是直接终止)
除数为0 运算溢出 数组下标越界 无效的参数 内存分配失败 异常处理的好处? 只能处理同步错误,不能处理异步事件 异步事件:多个事件可以同时发生, 如:鼠标点击和键盘击键 程序能在解决问题后继续执行(而不是直接终止) 增强程序的健壮性 东南大学计算机学院 1/17/2019
29
深入了解异常处理 异常对象 标准库异常类 异常匹配 重新抛出异常 异常规格表
30
异常对象 异常对象 异常对象的生存期 异常对象不是局部对象
异常对象是由编译器管理的特殊对象,它驻留在可被激 活的任意catch处理器都能访问的空间 异常对象的生存期 创建:throw 撤销:在对应的catch中完全处理了异常后 东南大学计算机学院 1/17/2019
31
dynamic_cast运算符抛出的异常
标准库异常类 Exception bad_alloc bad_cast bad_type_id invalid_argument length_error out_of_range logic_error overflow_error underflow_error runtime_error bad_exception 运行时错误 逻辑错误 运算上溢错误 运算下溢错误 非法参数错误 长度超限错误 下标越界错误 new运算符抛出的异常 dynamic_cast运算符抛出的异常 typeid运算符抛出的异常 意料之外的异常 东南大学计算机学院 1/17/2019
32
标准库异常类 标准库异常类提供的操作 创建异常类对象 复制异常类对象 默认的赋值操作
exception、bad_alloc、bad_cast只定义了默认的构造函 数,无法在创建对象时提供初值 其他异常类只定义了一个使用string类初始化的构造函 数 exception类提供了virtual函数what(),返回C风格字符串 char *类型的错误信息 东南大学计算机学院 1/17/2019
33
标准库异常类 捕获所有潜在的异常 catch(…)
catch(exception &)不能捕获所有异常,因为异常类可以 不继承exception类 东南大学计算机学院 1/17/2019
34
异常匹配 异常匹配规则 基类的指针可以指向派生类对象 可以利用多态性来处理异常 抛出异常 的类型 catch处理器中 异常参数的类型
类型相同 抛出异常 的类型 catch处理器中 异常参数的类型 继承 基类的指针可以指向派生类对象 可以利用多态性来处理异常 东南大学计算机学院 1/17/2019
35
异常匹配 异常匹配允许的类型转换 异常匹配不允许的类型转换 标准算术转换、自定义的类类型转换 抛出的异常 异常参数 派生类 基类
非const const 数组 数组指针 函数 函数指针 东南大学计算机学院 1/17/2019
36
异常匹配 异常匹配顺序 选择第一个可以处理该异常的catch处理器,而不必是 最佳匹配 存在函数调用链时,按函数执行顺序寻找匹配
void f(){ try { throw runtime_error(“runtime error"); } catch(logic_error &e) { cout<<"logic error"<<endl; } } int main(){ try{ f(); } catch(exception &e) { cout<<e.what()<<endl; } 函数f() 函数g() 函数k() 寻 找 异 常 匹 配 1/17/2019
37
异常匹配 异常与继承 可以在继承层次中定义异常类,通过基类指针指向不同派生类来统一处理异常 此时,不能访问派生类成员
bool flag = true; try { if(flag) throw runtime_error("runtime error"); else throw logic_error("logic error"); } catch(exception &e) { cout<<e.what()<<endl; } 此时,不能访问派生类成员 (基类指针只能访问基类成员) 1/17/2019
38
异常匹配 异常与继承 派生类的处理代码必须在基类的之前 bool flag = true; try { if(flag)
throw runtime_error("runtime error"); else throw logic_error("logic error"); } catch(exception &e) { cout<<"Exception Handle: "<<e.what()<<endl; } catch(runtime_error &e) //不会执行 { cout<<"Runtime_error Handle: "<<e.what()<<endl; } 东南大学计算机学院 1/17/2019
39
异常匹配 异常与继承 派生类的处理代码必须在基类的之前 bool flag = true; try { if(flag)
throw runtime_error("runtime error"); else throw logic_error("logic error"); } catch(runtime_error &e) { cout<<"Runtime_error Handle: "<<e.what()<<endl; } catch(exception &e) { cout<<"Exception Handle: "<<e.what()<<endl; } 东南大学计算机学院 1/17/2019
40
异常匹配 异常匹配不成功 异常没有经过try定义 没有catch处理器处理相应类型的异常
执行标准库函数terminate(),终止程序执行 ( <exception>头文件中定义) 通常情况下会导致程序非正常退出 异常没有经过try定义 没有catch处理器 执行标准库函数terminate() 东南大学计算机学院 1/17/2019
41
异常匹配 标准库函数terminate() 如果设置了set_terminate()函数,则调用其指定的函数
否则,调用abort函数,程序终止 不对自动对象和静态对象调用析构函数,导致内存泄漏 东南大学计算机学院 1/17/2019
42
重新抛出异常 为何要重新抛出异常(rethrow)? 重新抛出异常 需要在程序的不同部分对异常分别处理
异常处理器不能对异常进行完全处理 需要在多处对异常分别处理 重新抛出异常 需要在程序的不同部分对异常分别处理 通常存在多个函数调用时使用,使异常对象沿调用链向上传播 throw; 东南大学计算机学院 1/17/2019
43
重新抛出异常 调用函数 try { } 抛出的是原来的异常对象,而不是catch的形参 被调用函数 可能出现异常的语句块 特定类型的异常;
throw 可能出错的语句; 特定类型的异常; runtime_error() catch(特定类型的异常) { 处理异常的语句1; } exception &e throw; runtime_error() catch(特定类型的异常) { } 处理异常的语句2; 1/17/2019
44
异常抛出表 异常抛出表(Exception Specifications) 列举了函数的一系列可以抛出的异常类型
void f() throw( ExceptionA, ExceptionB, ExceptionC ) { … … } 函数只可以抛出异常抛出表中列出的异常类型 东南大学计算机学院 1/17/2019
45
异常抛出表 异常抛出表 何时使用异常抛出表 可以抛出任何异常类型:不提供异常抛出表 不能抛出任何异常类型:throw( )
若抛出的异常不在抛出表中,则调用unexpected()函数 (<exception>头文件中定义),该函数调用termination() 何时使用异常抛出表 一般情况下不建议使用 特殊情况:重写有抛出表的基类成员 东南大学计算机学院 1/17/2019
46
几种异常处理 栈展开 构造函数、析构函数和异常处理 动态内存分配的异常处理
47
栈展开 栈展开(Stack Unwinding) 栈:函数调用栈 throw 抛出异常 try 侦测异常 catch 处理异常
{ throw 异常; } catch(异常) … … 标准异常处理模式 当存在函数调用时,标准模式不能满足需要,情况变的复杂 1/17/2019
48
栈展开 try/catch均不在本函数内 标准模式 相应的catch不在本函数内 void f(){ try { throw 异常1; }
… … int main(){ f(); void f(){ try { throw 异常1; } catch(异常2) {… …} int main(){ f(); catch(异常1) void f(){ throw 异常1; } int main(){ try { f(); catch(异常1) … … try/catch均不在本函数内 标准模式 相应的catch不在本函数内 1/17/2019
49
栈展开 回顾:存在函数调用链时的异常匹配顺序 void f(){ try
{ throw runtime_error(“runtime error"); } catch(logic_error &e) { cout<<"logic error"<<endl; } } int main(){ try{ f(); } catch(exception &e) { cout<<e.what()<<endl; } 函数f() 函数g() 函数k() 寻 找 异 常 匹 配 东南大学计算机学院 1/17/2019
50
栈展开 栈展开(Stack Unwinding)
当被调用函数中抛出异常,但没有用try-catch将其捕获,则展开函数调用栈,沿函数执行顺序在调用函数内寻找try-catch块捕获该异常 void f(){ throw runtime_error(“runtime error"); } } int main(){ try{ f(); } catch( exception &e ) { cout<<e.what()<<endl; } 函数f() 函数g() 函数k() 捕 获 异 常 顺 序 1/17/2019
51
栈展开 抛出异常时的程序执行 抛出异常时,立即中断该函数的执行,查找匹配的catch处理器对异常进行处理 throw 抛出异常 try
侦测异常 catch 处理异常 中断函数执行 查找try 查找catch 异常匹配 东南大学计算机学院 1/17/2019
52
栈展开 抛出异常时的程序执行 中断函数执行 查找try 查找catch 不需要查找 需要查找 throw在try内 throw不在try内
void f(){ try { throw … …; } catch(…) {… …} void f(){ throw … …; } int main(){ try { f(); catch(…) {… …} 东南大学计算机学院 1/17/2019
53
栈展开 throw在try内 中断函数执行 查找try 查找catch 中断函数执行 查找catch 不匹配 退出当前程序
返回调用函数 匹配 执行catch处理器 释放当前函数的内存,并撤销局部对象 执行catch后的第一条语句 东南大学计算机学院 1/17/2019
54
栈展开 throw在try内 中断函数执行,判断throw在catch内 查找当前try对应的catch:不匹配 退出当前函数f()
void f(){ try { throw 异常1; } catch(异常2) {… …} 语句1; int main(){ f(); catch(异常1) catch(其他异常){……} 语句2; 中断函数执行,判断throw在catch内 查找当前try对应的catch:不匹配 查找当前try对应的catch:匹配 退出当前函数f() 返回调用函数main() 执行catch处理器 执行catch后的第一条语句
55
栈展开 throw不在try内 中断函数执行 查找try 查找catch 退出当前函数,沿函数调用链上行,找到第一个try main()
void g(){ throw 异常1; } void f(){ try { g(); catch(异常2) {… …} 语句1; int main(){ try { f(); } catch(异常1) {… …} catch(其他异常){……} 语句2; main() f() g() 捕 获 异 常 顺 序 东南大学计算机学院 1/17/2019
56
构造函数、析构函数和异常处理 构造函数抛出异常 该对象只是部分被构造 对象不完整 对try中已构造的自动对象调用析构函数
若自动对象包含成员对象,则成员对象的析构函数将会 被调用 若自动对象为数组对象,且异常发生时,数组对象只是 部分被构造,那么,数组中已经完成构造的对象才会被 析构 东南大学计算机学院 1/17/2019
57
构造函数、析构函数和异常处理 析构函数抛出异常 若析构函数抛出异常 析构函数释放资源,一般不会抛出异常 标准库类型保证其析构函数不会抛出异常
栈展开期间经常会调用析构函数,在处理异常时,又遇 到析构函数的异常 调用terminate函数 调用set_unexpected函数指定的函数 abort 析构函数释放资源,一般不会抛出异常 标准库类型保证其析构函数不会抛出异常 东南大学计算机学院 1/17/2019
58
动态内存分配的异常处理 new:分配内存 delete:释放内存 异常: 无法分配内存:new运算符,内存已满
东南大学计算机学院 1/17/2019
59
动态内存分配的异常处理 new失败 抛出bad_alloc异常(<new>头文件中定义)
double *ptr[50]; try { for(int i=0; i<50; i++) ptr[i] = new double[ ]; } catch( bad_alloc &e) cerr << e.what(); 内存不足时,new运算符函数抛出bad_alloc异常 bad allocation 使用catch处理器处理异常 东南大学计算机学院 1/17/2019
60
动态内存分配的异常处理 new失败的处理 使用set_new_handler(<new>头文件中定义)
不直接抛出bad_alloc异常,使用new处理器处理 new处理器的选择: 1. 释放其他动态分配的内存,并返回 new,尝试再次分配内存 2. 抛出bad_alloc异常 3. 调用abort或exit结束程序 东南大学计算机学院 1/17/2019
61
动态内存分配的异常处理 没有抛出异常:不需要throw-try-catch异常处理机制 double *ptr[50];
set_new_handler( customNewHandler ); for(int i=0; i<50; i++) { ptr[i] = new double[ ]; } void customNewHandler() { cerr<< “customNewHandler was called”; abort(); } 没有抛出异常:不需要throw-try-catch异常处理机制 东南大学计算机学院 1/17/2019
62
动态内存分配的异常处理 new运算符总结 new成功分配内存:返回指向内存的指针 new分配内存失败:
set_new_handler没有注册new处理器函数: 抛出bad_alloc异常 set_new_handler注册了new处理器函数: 调用new处理器 东南大学计算机学院 1/17/2019
63
动态内存分配的异常处理 异常 内存泄漏 局部自动变量总是会被销毁的
new分配内存 delete释放内存 内存泄漏 动态对象生命期 局部自动变量总是会被销毁的 auto_ptr类模板 局部自动对象 动态对象 … 指针成员 析构函数:delete指针成员指向的动态对象 当局部自动对象退出其作用域时销毁,其析构函数指示销毁其管理的动态对象,则会调用动态对象的析构函数
64
动态内存分配的异常处理 类模板auto_ptr (<memory>头文件中定义)
当一个auto_ptr类对象的析构函数被调用时,将delete其指针数据成员 局部自动对象 动态对象 … 指针成员 析构函数:delete指针成员指向的动态对象 东南大学计算机学院 1/17/2019
65
动态内存分配的异常处理 int main(){ auto_ptr<Integer> p( new Integer(7)); p->setInteger(9); cout << p->getInteger(); } class Integer{ public: Integer(int i=0); ~Integer(); void setInteger(int); int getInteger(); private: int value; }; p是main函数中的局部自动变量,在main结束时被销毁,其析构函数所指向的Integer对象被delete,此时,调用Integer对象的析构函数。 东南大学计算机学院 1/17/2019
66
其他异常处理技术
67
其他异常处理技术 忽视异常:一般用于为个人目的设计的程序 中断程序:经常使用 设置错误指示器:main函数
测试错误条件,传递错误信息,调用exit函数:经常使用 设置专门的函数处理:set_new_handler 东南大学计算机学院 1/17/2019
68
Exception Handling 教学要求与安排
全部要求 东南大学计算机学院 1/17/2019
69
作业 deadline: 5.31 24:00 H10:Ex. 16.25, 16.26, 16.35, 16.36 东南大学计算机学院
1/17/2019
Similar presentations