异常处理(二)

类别:编程语言 点击:0 评论:0 推荐:

                               异  常  处  理(二)
                                  Danny Kalev
                                          翻译:cppbug   [email protected]  

进入异常处理
正如你所见,传统C的错误处理方法并不适合C++,C++的一个设计目标就是让用C++进行大规模软件开发比C更好更安全。
C++的设计者们已经意识到缺乏合适的错误处理机制使得实现这一目标相当的困难。他们试图寻找一种完全摆脱C的错误处理缺陷的解决方案。其中的一种想法就是建立在当异常被触发的时候程序自动把控制权传递给系统。机制必须简单,并且它能够使程序员从不断的检查一个全局标记或者返回值的苦差事中解脱出来。另外,它还必须保证异常处理程序能够自动获得异常信息。最终它还要确保当一个异常没有在本地处理的时候,本地对象能够被适当的销毁,并且把它所持有的资源释放。
1989年,在多年的研究和多方建议下,异常处理进入C++。C++并不是第一个对结构化运行期错误处理进行支持的语言。早在20世纪60年代,PL/1就提供了一种内建的异常处理机制;Ada也在20世纪80年代提供了自己的异常处理,另外还有几种语言也做到了这一点。但是这些异常处理模型没有一个适合C++对象模型和程序结构。因此,被提议的C++异常处理是独一无二的,并且它已经作为了一种模型出现在一些新产生的语言之中。
异常处理机制的实现被证明是一种挑战。第一个C++编译器,cfront,在UNIX环境下运行。和许多UNIX编译器一样,它首先是作为一个翻译器把C++代码转换成C,接着再编译C代码。Cfront 4.0计划引入异常处理,然而,异常处理机制的实现是如此的复杂,以至于cfront 4.0的开发团队在用了一年时间设计它之后完全的放弃了这个项目。Cfront 4.0再也没有出台。然而,异常处理却成为了标准C++的有机组成部分。后来出现的一些编译器都支持了它。在接下来的部分里将会解释为什么在cfront以及任何编译器下实现异常处理是如此的困难。

实现异常处理所面临的挑战
实现异常处理所遇到的困难主要来自于以下几个因素:第一,实现必须保证对于某一异常的合适的handler被找到。
第二,异常对象必须是多态的;这样,当实现无法通过派生类对象定位handler的时候可以考虑基类的handler。这种需要表明必须引入运行期类型检测。然而那时C++还没有任何运行期类型检测的能力。因此这种能力必须首先被实现。
作为一个附加的复杂性,实现必须能够调用所有局部对象的析构函数。这个过程被称为stack unwinding 。因为早期的C++编译器首先要把C++源文件转换为纯C,然后再把C代码编译成机器码。异常处理的实现者们不得不用C来实现运行期类型鉴别和stack unwinding。幸运的是,这些障碍已经被克服。

应用异常处理
异常处理是一种灵活并且精巧的工具。它克服了C的传统错误处理方法的缺点并且能够被用来解决一系列运行期错误。但是,异常处理也像其他语言特性一样,很容易被误用。为了能够有效的使用这一特性,理解运行期机制是如何工作的以及相关的性能花费是非常重要的。接下来的部分里将会进入异常处理的内部并且论证如何使用这一工具来建立安全的应用系统。

异常处理要素
异常处理是一种把控制权从异常发生的地点转移到一个匹配的handler的机制。异常是内建数据类型变量或者是对象。异常处理机制包括四个部分:a try block,一个或多个和try block相关的handler,throw表达式,以及异常自己。Try block包含可能抛出异常的代码。例如:
 try
{
 int * p = new int[1000000]; //may throw std::bad_alloc
}
一个try block后面将跟有一个或多个catch语句或者说是handlers, 每一个handler 处理不同类型的异常。例如:
try
{
 int * p = new int[1000000]; //may throw std::bad_alloc
 //...
}
catch(std::bad_alloc& )
{
}
catch (std::bad_cast&)
{
}
handler仅仅被在try block中的throw表达式以及函数所调用。throw表达式包括一个关键字throw以及assignment expression。例如:
try
{
 throw 5; // 5 is assigned to n in the following catch statement
}
catch(int n)
{
}
throw表达式和返回语句很相似。empty throw是没有操作数的throw语句。例如:

throw;

在handler中的empty throw表明它在重新抛出异常,后面我们会讨论到它。另外,如果目前没有异常被处理,那么执行一个empty throw将会调用terminate()。

Stack Unwinding
当一个异常被抛出,运行时机制首先在当前的作用域寻找合适的handler。如果不存在这样一个handler,那么将会离开当前的作用域,进入更外围的一层继续寻找。这个过程不断的进行下去直到合适的handler被找到为止。此时堆栈已经被解开,并且所有的局部对象被销毁。如果始终都没有找到合适的handler,那么程序将会终止。注意,C++保证局部对象被适当的销毁仅仅是在抛出的异常被处理的情况下。一个未被扑获得异常是否引起局部对象的销毁由实现决定的。为了保证局部对象的析构函数在异常未被捕获情况下也能够被正常调用,你应该在main()里加入捕获任何异常的catch语句。例如:
int main()
{
  try
  {
    //...
  }
  catch(std::exception& stdexc)   // handle expected exceptions
  {
    //...
  }
  catch(...)   // ensure proper cleanup in the case of an uncaught exception
  {
  }
  return 0;
}
stack unwinding的过程就好比一个返回语句序列,每一个都返回相同的对象给它的调用者。

                                                                                                  ( 未完待续 )

本文地址:http://com.8s8s.com/it/it29099.htm