异常的基本思想
在小型程序中,一旦发生异常,一般是将程序立即中断运行,从而无条件释放所有资源。对于大型程序来说,运行中一旦发生异常,应该允许恢复和继续运行。
恢复的过程就是把产生异常所造成的恶劣影响去掉,中间可能要涉及一系列的函数调用链的退栈,对象的析构,资源的释放等。继续运行就是异常处理之后,在紧接着异常处理的代码区域中继续运行。
在C++中,异常是指从发生问题的代码区域传递到处理问题的代码区域的一个对象。
基本思想:
(1)实际的资源分配(如内存申请或文件打开)通常在程序的低层进行。
(2)当操作失败、无法分配内存或无法打开一个文件时.在逻辑上如何进行处理通常是在程序的高层,中间还可能有与用户的对话。
(3)异常为从分配资源的代码转向处理错误状态的代码提供了一种表达方式。如果还存在中间层次的函数,则为它们释放所分配的内存提供了机会,但这并不包括用于传递错误状态信息的代码。从中可以看出,C++异常处理的目的,是在异常发生时,尽可能地减小破坏,周密地善后,而不去影响其它部分程序的运行。-这在大型程序中是非常必要的。
例如普通的程序调用关系,如程序出现异常现象,异常只能在发生的函数中进行处理,无法直接传递到其他函数中,而且善后处理也十分困难。
使用异常的步骤:
(1)定义异常(try语句块) 将那些可能产生错误的语句框定在try语句中;
(2)定义异常处理(catch语句块)
将异常处理的语句放在catch块中,以便异常被传递过来时就处理它;
(3)抛掷异常(throw语句)
检测是否产生异常,若产生异常,则抛掷异常。
异常处理机制:
(1)运行到throw语句时,检测是否发生异常,如果发生异常,则看该异常是否在本函数体中的try语句块中已经定义,如果已经定义,则到本函数中找catch语句,检查是否捕获.如果本函数中的catch语句存在捕获该异常的语句,则执行异常处理语句.如果本函数体中的try语句块中没有定义该异常,或者本函数体中的catch语句不存在捕获该异常的语句,则沿函数链向上传递(其他函数中的try语句定义该函数的异常,具体见以下实例),直到异常被捕获,并执行异常处理语句为止.如果始终没有被捕获,则异常将被系统的terminate()函数调用,后者按常规再调用abort()。你不管,系统就要插手啦!
Example:
void f()
{
try { g(); }
catch(Range)
{ //…… }
catch (Size)
{ //…… }
catch (…)
}
void g()
{
h();
}
void h()
{
try
{
h1();
}
catch(Size)
{//...
throw 10;
}
catch(Matherr)
{//...
}
}
void h1()
{
//...
throw(Size);
try
{//...
throw Range;
h2();
h3();
}
catch(Size)
{
//...
throw;
}
}
void h2()
{
//...
throw Matherr;
}
void h3()
{
//...
throw Size;
}
函数f()中的catch(...)块,参数为省略号,定义一个"默认"的异常处理程序。通常这个处理程序应在所有异常处理块的最后,因为它与任何throw都匹配,目的是为避免定义的异常处理程序没能捕获抛掷的异常而使程序运行终止。
函数h()中的catch(Size)块,包含有一个抛掷异常语句throw 10,当实际执行这条语句时,将沿着调用链向上传递被函数f()中的catch(...)所捕获。如果没有f()中的catch(...),那么,异常将被系统的terminate()函数调用,后者按常规再调用abort()。
函数h1()中的抛掷throw Size,由于不在本函数的try块中,所以只能沿函数调用链向上传递,结果被h()中的catch(Size)捕获。函数h1()中的抛掷throw Range,在try块中,所以首先匹配try块后的异常处理程序,可是没有被捕获,因而它又沿函数调用链向上,在函数f()中,catch(Range)块终于捕获了该抛掷。函数h1()中的catch(Size)块,包含一个抛掷throw,没有带参数类型,它表示将捕获到的异常对象重新抛掷出去,于是,它将沿函数调用链向上传递,在h()中的catch(Size)块,捕获了该抛掷。
函数h2()中的抛掷throw Matherr,首先传递给h1()中的catch块组,但未能被捕获,然后继续沿调用链向上,在h()中的catch(Matherr)块,捕获了该抛掷。函数h3()中的抛掷throw Size,向上传递给h1()中的catch块组,被catch(Size)块捕获。
使用异常的方法
可以把多个异常组成族系。构成异常族系的一些实力又数学错误异常族系和文件处理错误异常族系。在C++代码中把异常组在一起有两种方式:异常枚举族系和异常派生层次结构。 例如,下面的代码是一个异常枚举族系的例子: enmu FileErrors{nonExist,wrongformat,diakSeekError,...}; int f() { try{ //... throw wrongFormat;} catch(FileErrors fe) { switch(fe) { case nonExist: //... case wrongFormat: //... case diskSeekError: //... } } //... } 在try块中有一个throw,它抛掷一个FileError枚举中的常量。这个抛掷可被catch(FileErrors)块捕获到,接着后者执行一个switch,对照情况列表,匹配捕获到的枚举常量值。上面的异常族系也可按异常派生层次结构来实现,如下例所示: class FileErrors{}; class NonExist:public FileErrors{}; class WrongFormat:public FileErrors{}; class DiskSeekError:public FileErrors{}; int f() { try { //... throw WrongFormat;} catch(NonExist) { //...} catch(DiskSeekError) { //...} catch(FileErrors) { //...} //... } 上面的各异常处理程序块定义了针对类NonExist和DiskSeekError的派生异常类对象,针对FileErrors的异常处理,既捕获FileErrors类对象,也捕获WrongFormat对象。异常捕获的规则除了前面所说的,必须严格匹配数据类型外,对于类的派生,下列情况可以捕获异常: (1) 异常处理的数据类型是公有基类,抛掷异常的数据类型是派生类; (2) 异常处理的数据类型是指向公有基类的指针,抛掷异常的数据类型是指向派生类的指针。
|