📄 13章 异常处理.txt
字号:
输出结果:
Function throwException
Exception handled in function throwException
Exception handled in main
Program control continues after catch in main
图 13.2 再抛出异常
13.9 异常指定
异常指定可以由指定函数抛出一列异常:
int g( float h ) throw (a, b, c)
{
// function body
}
可以限制从函数抛出的异常类型。函数声明中可以指定异常类型作为异常指定(也称为抛出表,throw list)。异常指定列出可抛出的异常。函数可以抛出指定异常或派生类型。尽管这样好像保证不会抛出其他异常类型,但其实也可以抛出其他异常类型。如果抛出异常指定中没有列出的异常,则调用函数unexpected。
将throw(即空异常指定)放在函数的参数表之后表示该函数不抛出任何异常。但这种函数仍然可以抛出异常,这时也调用函数unexpected。
常见编程错误13.11
如果抛出函数异常指定中没有的异常,则调用函数unexpcted不带异常指定的函数可以抛出任何异常:
void g(); // this function can throw any exception
unexpected函数的含义可以调用函数set_unexpected重新定义。
异常处理的一个有趣方面是函数在throw表达式中包含函数异常指定中未列出的异常时,编译器不产生语法错误。函数在执行时抛出这个异常之后才会捕获错误。
如果函数抛出特定类的类型的异常,则函数也可以抛出用public继承从该类派生的所有类异常。
13.10 处理意外异常
函数unexpected调用set_unexpected函数指定的函数。如果没有用set_unexpected函数指定函数,则默认调用terminate。
函数terminate可以显式调用,在无法捕获抛出的异常时、在异常处理期间打乱堆栈时、作为调用unexpected的默认操作时或在异常导致堆栈解退时析构函数抛出异常的情况下都会调用terminate。
函数set_terminate可以指定调用terminate时调用的函数,否则terminate调用abort。
函数赞set_terminate和set_unexpected的函数原型分别在头文件<terminate.h)和<unexpected.h>中。
函数set_terminate和set_unexpected分别返回terminatee和unexpected调用的最后一个函数的指针。这样就使程序员可以保存函数指针,以便后面恢复。
函数set_terminate和set_unexpected取函数指针为参数。每个参数指向返回类型为void和无参数的函数。
如果用户自定义终止函数的景后一个操作不是退出程序,则执行用户自定义终止函数的其他语句之后自动调用abort函数终止程序。
13.11 堆栈解退
特定范围中拄出异常而未捕获时,函数调用堆栈解退,试图在外层try/catch块中捕获这个异常。函数调用堆栈解退表示未捕获异常的函数终止,删除函数的所有局部变量,控制返回函数调用点。如果函数调用点在try块中,则试捕获这个异常,如果函数调用点不在try块中或没有捕获这个异常,则再次发生堆栈解退。上节曾介绍过,如果程序不捕获异常,则调用函数terminate以终止程序。图13.3的程序演示了堆栈解退。
1 // Fig. 13.3: fig13_03.cpp
2 // Demonstrating stack unwinding.
3 #include <iostream>
4 #include <stdexcept>
5
6 using namespace std;
7
8 void function3() throw ( runtime_error )
9 {
10 throw runtimeerror( "runtime_error in function3" );
11 }
12
13 void function2() throw ( runtimeerror )
14 {
15 function3();
16 }
17
18 void functionl() throw ( runtime_error )
19 {
20 function2();
21 }
22
23 int main()
24 {
25 try {
26 functionl();
27 }
28 catch ( runtime_error e }
29 {
30 cout << "Exception occurred: "<< e.what() << endl;
31 }
32
33 return 0;
34 }
输出结果:
Exception occurred: runtime_error in function3
图 13.3 堆栈解退
在main中,第25行的try块调用function1。然后第18行定义的function1调用funclion2。然后第13行定义的function2调用function3。function3的第10行抛出exception对象。由于第10行不在try块中,因此发生堆栈解退,function3终止.控制返回function2(第15行)。由于第15行不在try块中,再次发生堆栈解退,function2终止,控制返回funetio1(第20行)。由于第20行不在try块中,又一次发生堆栈解退,fonetion1终止,控制返回main(第26行)。由于第26行在try块中,可以捕获异常,并在try块后面第一个匹配的catch处理器中处理(第28行)。
13.12 构造函数、析构函数与异常处理
首先要处理前面已经提到但还没有完全解决的问题。构造函数中发现错误时会发生什么情况?例如,String构造函数在new失败和无法取得保持String的内部表示所需空间时如何响应?问题是构造函数无法返回数值,如何让外部知道对象没有顺利构造呢,一种方案是返回没有正确构造的对象,希望对象使用者通过相应测试确定该对象是不能使用的对象。另一种方案是在构造函数之外设置一些变量。抛出的异常向外部传递失败的构造函数信息.并负责处理这个故障。
要捕获异常,异常处理器要访问所抛出对象的复制构造函数(默认成员的副本也有效)。
构造函数中抛出异常时,对抛出异常之前要构造的对象调用析构函数。
抛出异常之前每个try块中构造的自动对象都调用析构函数。异常在开始执行处理器时处理,这时堆栈解退一定已经完成。如果堆栈解退调用析构函数而抛出异常,则调用terminate。如果对象有成员函数.且如果异常在外层对象构造完成之前抛出,则执行发生异常之前所构造成员对象的析构函数。如果发生异常时部分构造了对象数组,则只调用已构造数组元素的析构函数。
异常可能越过通常释放资源的代码,从而造成资源泄漏。要解决这个问题,一种方法是在请求资源时初始化一个局部对象,发生异常时,调用析构函数井释放资源。
要捕获析构函数中抛出的异常,可以将调用析构函数的函数放在try块中,并提供相应类型的catch处理器。所抛出对象的析构函数在异常处理器执行完毕之后执行。
13.13 异常与继承
可以从共用基类派生各种异常类。如果catch捕获基类类型异常对象的指针或引用,则也可以捕获该基类所派生的异常对象的指针或引用。这样允许相关错误的多态处理。
调试与调试提示13.2
利用异常继承使异常处理器可以用相当简单的符号捕获相关错误。虽然可以捕获每个派生类异常对象的指针或引用,但更简练的方法是捕获基类异常对象的指针或引用,另外.如果程序员忘记测试一个或几个派生类指针或引用,则捕获每个派生类异常对象的指针或引用容易造成错误。
13.14 处理new故障
处理new故障的方法有多种。到目前为止.我们介绍过用宏assert测试new返回的值。如果返回值为0,则assert宏终止程序。这不是处理new故障的健壮机制,它不允许我们用任何方法从故障恢复。ANSI/ISO C++草案标准指定,出现new故障时抛出bad_alloc异常(在头文件<new>中定义)。但许多编译器目前还不支持草案标准,仍然在new故障时返回0。本节介绍三个new故障的例子。第一个例子用Microsoft的Visual C++5.0编译,仍然在new故障时返回0。第二和第三个例子用Metrowerks Code Warrior Professional Releasel中的C++编译器编译,出现new故障时,它抛出bad_alloc异常。我们在不同的运行Windows95的计其机上测试三个程序,每台机器的内存和硬盘空间各不相同。
图13.4演示了new请求分配内存失败时返回0。第10行的for结构循环10次,每次循环分配5000000个double值的数组(即40000000字节,因为double通常为8个字节)。第13行的if结构测试每次new操作中是否顺利分配内存。如果new请求内存失败并返回0,则打印”Memory allocation failed\n”消息,循环终止。
1 // Fig. 13.4: fig13_04.cpp
2 // Demonstrating new returning 0
3 // when memory is not allocated
4 #include <iostream.h>
5
6 int main()
7 {
8 double *ptr[ 10 ];
9
10 for (int i = 0; i < 10; i++ ) {
11 ptr[ i] = new double[ 5000000 ];
12
13 if ( ptr[ i ] == 0 ) { // new failed to allocate memory
14 cout << "Memory allocation failed for ptr[ "
15 << i << "]\n";
16 break;
17 }
18 else
19 cout << "Allocated 5000000 doubles in ptr[ "
20 << i << "]\n";
21 }
22
23 return 0;
24 }
输出结果:
Allocated 5000000 doubles in ptr[ 0 ]
Allocated 5000000 doubles in ptr[ 1 ]
Memory allocation failed for ptr[ 2 ]
图 13.4 new 请求内存失败时返回 0
输出表示new失败和循环终止之前只进行了两欠循环。不同系统中的输出可能不同,取决于实际内存和可用的虚拟内存的磁盘空间。
图13.5演示了new请求内存失败时返回bad_alloc。第12行的for结构循环0次,每次循环分配5 000 000个double值的数组(即40 000 000字节,因为double通常为8个字节)。如果new失败并抛出bad_alloc异常,则终止循环,程序继续第18行的异常处理泫程捕获和处理异常,打印"Exception occurred"消息,然后exception what()返回异常特定消息的字符串(对于bad_alloc为"Alloccation Failure")。输出表示new失败和抛出bad_alloc异常之前只进行了三次循环。不同系统中的输出可能不同,取决于实际内存和可用的虚拟内存的磁盘空间。
1 // Fig. 13.5: fig13_05.cpp
2 // Demonstrating new throwing bad alloc
3 // when memory is not allocated
4 #include <iostream>
5 #include <new>
6
7 int main()
8 {
9 double *ptr[ 10 ];
10
11 try {
12 for( int i = 0; i < 10; i++ ) {
13 ptr[ i ] = new double[ 5000000 ];
14 cout << "Allocated 5000000 doubles in ptr[ "
15 << i << " ]\n";
16 }
17 }
18 catch ( bad_alloc exception ) {
19 cout << "Exception occurred:"
20 << exception.what() << endl;
21 }
22
23 return 0;
24 }
输出结果:
Allocated 5000000 doubles in ptr[ 0 ]
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -