C++为什么要引入异常处理机制

`#1.异常处理的困难
在程序设计中,错误时不可避免的。及时有效的发现错误,并作出适当的处理,无论是在软件的开发阶段还是在维护阶段都是至关重要的。错误修复技术是提高代码健壮性的最有效的方法之一。

程序员往往忽视错误处理,并不是因为程序员真的认为自己的程序不会出错,而是因为出错处理是在不是一件轻松的事。编写出错处理的代码,一方面会分散处理“主要”问题的精力,另一方面会引起代码的膨胀,给阅读和维护带来困难。而且,尽可能详细地考虑出错的情形也是一件费时费力的事情。

2.C语言处理异常的常用方法

在C语言中,有一些处理错误的常用方法。例如,使用C标准库的宏断言assert()作为出错处理的方法。在开发过程中,使用这个宏进行必要的条件检测,项目完成后可以使用#define NDEBUG来禁用断言assert()。随着程序规模的扩大,使用宏来进行出错处理的复杂性也在增加。

如果在当前上下文环境中,程序猿可以明确地掌握每一个具体步骤的运行结果,出错处理就变得十分明确和容易了。若错误问题发生时在一定的上下文环境中得不到足够的信息,则需要从更大的上下文环境中提取出错误处理信息。C语言处理这类情况通常有三种典型的方法。

(1)出错的信息可以通过用函数返回值获得。如果函数返回值不能用,则可设置一全局错误判断标志(标准C语言中errno()和perror()函数支持这一方法)。由于对每个函数都进行错误检查十分繁琐,并增加了程序的混乱度,程序设计者可能简单地忽略这些出错信息。另外,来自偶然出现异常的函数的返回值可能并不能提供什么有价值的信息。

(2)可使用C标准库中一般不常用的信号处理系统,利用signal()函数(判断事件发生类型)和raise()函数(产生事件)。由于信号产生库的使用者必须理解和安装合适的信号处理,所以使用者两个函数进行出错处理时应紧密结合各信号产生库。对于大型项目而言,不同库之间的信号可能会产生冲突。

(3)使用C标准库中非局部的跳转函数:setjmp()和longjmp()。setjmp()函数可在程序中存储一典型的正常状态,如果程序发生错误,longjmp()可恢复setjmp()函数的设定状态,从而实现goto语句无法实现的“长跳转”。事先被存储的地点在恢复时,可以得知是从哪里跳转过来的,也就是说,可以确定错误发生的地点。

参考下面的使用setjmp()和longjmp()实现“长跳转”的例子。

#include <setjmp.h>

#include <iostream>
using namespace std;

class Game{
public:
    Game(){
        cout<<"game()"<<endl;
    }
    ~Game(){
        cout<<"~game()"<<endl;
    }
};

jmp_buf jmpBuf;

void test(){
    Game game;
    for(int i=0;i<3;++i)
        cout<<"there is no interesting game"<<endl;
    longjmp(jmpBuf,1);  
    cout<<"after jump"<<endl;
}

int main(){
    if(setjmp(jmpBuf)==0){
        cout<<"one, two, three..."<<endl;
        test();
    }
    else
        cout<<"It is fantastic"<<endl;
}

程序输出:

one, two, three...
game()
there is no interesting game
there is no interesting game
there is no interesting game
~game()
It is fantastic

setjmp()和longjmp()函数实现goto无法实现的非局部的跳转,其实原理很简单。

(1)setjmp(j)设置“jump”点,用正确的程序上下文填充jmp_buf对象j。这个上下文包括程序存放位置、栈和框架指针,其它重要的寄存器和内存数据。当初始化完jump的上下文,setjmp()返回0值。

(2)以后调用longjmp(j,r)的效果就是一个非局部的goto跳转或“长跳转”,程序将跳转到由j描述的上下文处(也就是到那原来设置j的setjmp()处)。当作为长跳转的目标而再次被调用时,setjmp()返回r或1(如果r设为0的话)。(记住,setjmp()不能在这种情况时返回0)。

程序中,控制流从函数test内部跳转到了main函数的setjmp()处,test函数中的cout<<"after jump"<<endl;并没有被执行。

3.C++为何引入异常处理机制

在早些时期,C++本身并没有处理运行期错误的能力。取而代之的是那些传统的C的异常处理方法。这些方法可以被归为三类设计策略:

(1)函数返回一个状态码来表明成功或失败;
(2)把错误码赋值给一个全局标记并且让其他的函数来检测;
(3)终止整个程序;

上述的任何一个方法在面向对象环境下都有明显的缺点和限制,如繁琐的检测函数返回值和全局的错误码,程序崩溃等。其中的一些根本就不可接受,尤其是在大型应用程序中。因此C++的异常处理就在这个背景下产生的。C++自身有着非常强的纠错能力,发展到如今,已经建立了比较完善的异常处理机制。

C++之父Bjarne Stroustrup在《The C++ Programming Language》中讲到:一个库的作者可以检测出发生了运行时错误,但一般不知道怎样去处理它们(因为和用户具体的应用有关);另一方面,库的用户知道怎样处理这些错误,但却无法检查它们何时发生(如果能检测,就可以在用户的代码里处理了,不用留给库去发现)。

Bjarne Stroustrup说:提供异常的基本目的就是为了处理上面的问题。基本思想是:让一个函数在发现了自己无法处理的错误时抛出(throw)一个异常,然后它的(直接或者间接)调用者能够处理这个问题。

[1]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008[P353-P355]
[2]百度百科.setjmp
[3]http://www.uml.org.cn/c%2B%2B/201305272.asp

你可能感兴趣的:(C++为什么要引入异常处理机制)