文档介绍:第十章异常处理
面向对象的C++程序设计语言可以编写大型和十分复杂的程序,这样的程序往往会产生一些很难查找的甚至是无法避免的运行时错误。当发生运行时错误时,不能简单地结束程序运行,而是退回到任务的起点,指出错误,并由用户决定下一步工作。面向对象的异常处理(exception handling)机制是C++语言用以解决这个问题的有力工具。函数执行时,放在try(测试)程序块中的任何类型的数据对象发生异常,都可被throw块抛出,随即沿调用键退回,直到被catch块捕获,并在此执行异常处理,报告出现的异常等情况。从抛出到捕获,应将各嵌套调用函数残存在栈中的自动对象、自动变量和现场保护内容等进行清除。如果已退到入口函数还未捕获则由abort()来终结入口函数。
异常处理在C++编程中已经普遍采用,成为提高程序健壮性的重要手段之一。本章将学习异常处理的一般方法。
异常的概念
在前面很多章节,都提到程序的健壮性,在程序运行时,经常会发生一些异常现象。比如在栈运行时,需要压栈,但栈空间满了;需要出栈,但栈空间已经空了。那时处理的方法是太简单化了:使用assert()函数来退出整个程序。这一方法对单个的程序或许是可以接受的,但对一个大型软件是不能接受的,大型软件不可能没有错误,也不可能在排除所有的错误后才投入使用。找出所有潜在的运行时错误几乎是不可能的,能够做到的是预计可能发生什么类型的错误,并在错误发生时停止发生错误的操作,对它进行处理,回到调用任务的起点,而程序的其它部分仍然继续运行。比如一个银行系统,不能因个别用户发生运行时错误而停止整个程序。当然这些错不是指简单的输入错,输入错可以重输,直到正确;或者多次错,就封闭使用。有些运行时错误是可以完全准确地在编程时预料到有可能在那里发生的,那么很自然地在编程时就在该程序段中添加了有关的处理。这里所讲的异常(exception)是程序可能检测到的、运行时的不正常情况,如存储空间耗尽、数组越界、被0除等等。可以预见可能发生在什么地方,但是无法确知怎样发生和何时发生。特别在一个大型的程序(软件)中,程序各部分是由不同的小组编写的,它们由公共接口连接,错误可能就发生在相互的配合上,也可能发生在事先根本想不到的个别的条件组合上。本章介绍的技术,尽管是为大型软件工程开发所发展的,但是它在标准C++中已经成为一个标准的技术,在任何规模的程序中都可以使用。
C++提供了一些内置的语言特性来产生(raise)或抛出(throw)异常,用以通知“异常已经发生”,然后由预先安排的程序段来捕获(catch)异常,并对它进行处理。这种机制可以在C++程序的两个无关(往往是独立开发)的部分进行“异常”通信。由程序某一部分引发了另一部分的异常,这一异常可回到引起异常的部分去处理(沿着程序函数的调用链)。这也是分清处理责任的好办法。
到现在为止,本章是从宏观应用上介绍异常处理,尽管这是最重要的,否则无法理解异常处理的优点和在软件中怎样使用它。但是异常处理编程的语法细节也是非常重要的,从下节开始,重点介绍异常处理编程的语法细节。
异常处理的机制
仍然以栈为例,参见【】,压栈和出栈函数模板为:
template <typename T>void Stack<T>::Push(const T &data){
assert(!IsFull());
elements[++top]=data;
}
template<typename T>T Stack<T>::Pop(){
assert(!IsEmpty());
return elements[top--];
}
首先,在C++中异常往往用类(class)来实现,异常类的声明如下:
template <typename T>class popOnEmpty{...};
template <typename T>class pushOnFull{...};
这样不再是一测到栈满或空就退出程序了,而是抛出一个异常。
template <typename T>void Stack<T>::Push(const T&data){
if(IsFull()) throw pushOnFull<T>(data);//注意加了括号,是构造一个无名对象
elements[++top]=data;
}
template<typename T>T Stack<T>::Pop(){
if(IsEmpty()) throw popOnEmpty<T>();
return elements[top--];
}
注意pushOnFull是类,C++要求抛出的必须是对象,所以必须有“()”,