我们在编写应用软件时,不仅要保证软件的正确性,而且应该具有容错能力。也就是说,不仅在正确的环境条件下、在用户正确操作时要运行正确,而且在环境条件出现意外或用户使用操作不当的情况下,也应该有正确合理的表现,不能轻易出现死机,更不能出现灾难性的后果。由于环境条件和用户操作的正确性是没有百分之百保障的,所以我们在设计程序时,就要充分考虑到各种意外情况,并给予恰当的处理。这就是我们所说的异常处理。 程序运行中的有些错误是可以预料但不可避免的,例如内存空间不足、硬盘上的文件被移动、打印机未连接好等由系统运行环境造成的错误。这时要力争做到允许用户排除环境错误,继续运行程序;至少要给出适当的提示信息,提供更友好的交互。这就是异常处理程序的任务。 在一个大型软件中,由于函数之间有着明确的分工和复杂的调用关系,发现错误的函数往往不具备处理错误的能力。这时它就引发一个异常,希望它的调用者能够捕获这个异常并处理这个错误。如果调用者也不能处理这个错误,还可以继续传递给上级调用者去处理,这种传播会一直继续到异常被处理为止。如果程序始终没有处理这个异常,最终它会被传到C运行系统那里,运行系统捕获异常后通常只是简单地终止这个程序。 C的异常处理机制使得异常的引发和处理不必在同一函数中,这样底层的函数可以着重解决具体问题,而不必过多地考虑对异常的处理。上层调用者可以在适当的位置设计对不同类型异常的处理。 为了加强程序的可读性,使函数的用户能够方便地知道所使用的函数会抛掷哪些异常,可以在函数的声明中列出这个函数可能抛掷的所有异常类型,例如:voidfun()throw(A,B,C,D); 这表明函数fun()能够且只能够抛掷类型A、B、C、D及其子类型的异常。如果在函数的声明中没有包括异常接口声明,则此函数可以抛掷任何类型的异常,例如:voidfun(); 一个不抛掷任何类型异常的函数可以进行如下形式的声明:voidfun()throw(); C异常处理的真正能力,不仅在于它能够处理各种不同类型的异常,还在于它具有为异常抛掷前构造的所有局部对象自动调用析构函数的能力。 如果try块中发生的异常没有匹配的catch处理程序,或者不在try块中的语句发生异常,则包含该语句的函数会立即终止执行,试图在它的主调函数中查找包含异常的try块。该过程称为栈展开。catch语句如果匹配异常对象成功,在完成了对catch语句的参数的初始化(对传值参数完成了参数对象的copy构造)之后,对同层级的try块执行栈展开。 当特定作用域(scope)中抛出异常但是却没有捕获时(该scope内的throw没有try包裹,没有被catch,大部分情况是在主调函数中被catch),就会展开函数调用栈,试图在外层的try。。。catch块中捕获这个异常。函数调用栈展开意味着没有捕获该异常的函数终止执行,该函数内的所有局部变量被删除,控制返回到最初调用该函数的语句。如果该函数调用语句包含在try块中,就会试图捕获该异常。如果该函数调用语句没有包含在try块中,则再次发生栈展开。如果一直没有可捕获该异常的catch处理程序,就会调用terminate函数终止程序的执行。Demonstratingstackunwinding。function3throwsruntimeerrorvoidfunction3()throw(runtimeerror){coutInfunction3notryblock,stackunwindingoccurs,returncontroltofunction2(caller)throwruntimeerror(runtimeerrorinfunction3);noprint}function2invokesfunction3voidfunction2()throw(runtimeerror){coutfunction3iscalledinsidefunction2function3();stackunwindingoccurs,returncontroltofunction1(caller)}function1invokesfunction2voidfunction1()throw(runtimeerror){coutfunction2iscalledinsidefunction1function2();stackunwindingoccurs,returncontroltomain(caller)}demonstratestackunwindingintmain(){invokefunction1try{coutfunction1function1();callfunction1whichthrowsruntimeerror}catch(runtimeerrorerror)handleruntimeerror{coutExceptionoccurred:error。what()coutE}} main函数中,try块调用functionl。然后,function1调用funclion2,function2转而调用funclion3。function3抛出一个rumimeerror对象。然而,由于该行的throw语句没有包含在try块中,因此发生栈展开,function3在throw处终止,然后将控制返回到function2中调用function的语句。由于该行没有包含在try块中,因此再次发生栈展开,function2在该行终止,并将控制返回到function1中调用function2的语句。由于该行没有包含在try块中,因此又一次发生栈展开,function1在函数调用处终止,并将控制返回到main函数中调用function1的语句。由于该语句包含在捕获并处理该异常。使用的what函数以显示异常信息。 某一scope内抛出异常,在该scope内因未被try包裹,未被catch,引发栈展开(stackunwinding),销毁局部对象,为局部对象调用析构函数:classMyException{public:MyException(conststringmessage):message(message){}MyException(){}conststringgetMessage()const{}private:};classDemo{public:Demo(){coutConstructorofD}voidprint(){coutIamaD}Demo(){coutDestructorofD}};Demofunc()throw(MyException){D如果在与d相同的作用域内抛出异常且未被catch,栈展开,被析构DemopnewD如果在与d相同的作用域内抛出异常且未被catch,栈展开,不被析构D抛如果在与d相同的作用域内抛出异常且未被catch,栈展开,被析构coutThrowMyExceptioninfunc()throwMyException(exceptionthrownbyfunc());抛出异常,在该scope内因未被try包裹,未被catch,引发栈展开exit(1);析构函数不会被调用}intmain(){coutItry{Demopfunc();main调用func,由main处理异常,在func中引发栈展开pprint();free(p);}catch(MyExceptione){coutCaughtanexception:e。getMessage()}catch(charstr){}coutResumetheexecutionofmain()getchar();return0;}InmainfunctionConstructorofDemoThrowMyExceptioninfunc()DestructorofDemoCaughtanexception:exceptionthrownbyfunc()Resumetheexecutionofmain() 栈展开(unwinding)是指当前的try。。。catch。。。块匹配成功或者匹配不成功异常对象后,从try块内异常对象的抛出位置(大部分情况是被调函数中),到try块的开始处的所有已经执行了各自构造函数的局部对象,按照构造生成顺序的逆序,依次被析构。如果当前函数内对抛出的异常对象匹配不成功,则从最外层的try语句到当前函数体的起始位置处的局部对象也依次被逆序析构,实现栈展开,然后再回退到调用栈的上一层函数内从函数调用点开始继续处理该异常。 由于线程执行时,被调用的函数的参数、返回地址、局部变量等都是依函数调用次序保存在函数调用栈(即线程运行时栈)上。当前被调用函数的参数、局部变量名字可以覆盖掉早前调用函数的同名变量,看起来就是只有当前函数内的名字可以访问,早前调用的函数内部的名字都不可访问,就像磁带被卷起。异常处理时按照函数调用顺序的逆序析构,依次析构各个被调函数的局部变量,就类似把已经卷起的磁带再展开,抹去上面记录的数据,故此栈展开得名。 End