C++ 异常处理

来源:互联网 发布:新手学美工 编辑:程序博客网 时间:2024/06/03 14:37

异常,让一个函数可以在发现自己无法处理的错误时抛出一个异常,希望它的调用者可以直接或者间接处理这个问题。而传统错误处理技术,检查到一个局部无法处理的问题时:

1.终止程序(例如atol,atoi,输入NULL,会产生段错误,导致程序异常退出,如果没有core文件,找问题的人一定会发疯)

2.返回一个表示错误的值(很多系统函数都是这样,例如malloc,内存不足,分配失败,返回NULL指针)

3.返回一个合法值,让程序处于某种非法的状态(最坑爹的东西,有些第三方库真会这样)

4.调用一个预先准备好在出现"错误"的情况下用的函数。

第一种情况是不允许的,无条件终止程序的库无法运用到不能当机的程序里。第二种情况,比较常用,但是有时不合适,例如返回错误码是int,每个调用都要检查错误值,极不方便,也容易让程序规模加倍(但是要精确控制逻辑,我觉得这种方式不错)。第三种情况,很容易误导调用者,万一调用者没有去检查全局变量errno或者通过其他方式检查错误,那是一个灾难,而且这种方式在并发的情况下不能很好工作。至于第四种情况,本人觉得比较少用,而且回调的代码不该多出现。

使用异常,就把错误和处理分开来,由库函数抛出异常,由调用者捕获这个异常,调用者就可以知道程序函数库调用出现错误了,并去处理,而是否终止程序就把握在调用者手里了。

但是,错误的处理依然是一件很困难的事情,C++的异常机制为程序员提供了一种处理错误的方式,使程序员可以更自然的方式处理错误。
点击查看原文

C异常处理

传统错误处理方式:
1.终止程序(除数为0)
2.返回一个表示错误的值,附加错误码(GetLastError())
3.返回一个合法值,让程序处于某种非法的状态(坑爹的atoi())
4.调用一个预先准备好在出现"错误"的情况下用的函数(回调函数)。
5.暴力解决方式:abort()或者exit()
6.使用goto语句
7.setjmp()和longjmp()组合

C++ 异常处理

C++异常机制使用了三个新的关键字 (SEH(结构化异常处理))

  • try ──标识可能出现的异常代码段
  • throw ──抛出一个异常
  • catch ──标识处理异常的代码段
1.异常的抛出和捕获
  • 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个处理代码。
  • 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
  • 抛出异常后会释放局部存储对象,所以被抛出的对象也就还给系统了, throw 表达式会初始化一个抛出特殊的异常对象副本(匿名对象),异常对象由编译管理,异常对象在传给对应的 catch 处理之后撤销。

异常的抛出: 
throw 必须在 try 代码块中.后边跟的值决定抛出异常的类型。

捕获异常的代码一般如下:

try {    throw E();}catch (H h) {     //何时我们可以能到这里呢}
2.栈展开

抛出异常的时候,将暂停当前函数的执行,开始查找对应的匹配catch子句。

  • 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则处理。没有则退出当前函数栈,继续在调用函数的栈中进行查找。
  • 不断重复上述过程。若到达main函数的栈,依旧没有匹配的,则终止程序。
  • 上述这个沿着调用链查找匹配的catch子句的过程称为栈展开。*
  • 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。
3.异常捕获的匹配规则

异常对象的类型与catch说明符的类型必须完全匹配。只有以下几种情况例外

  • 允许从非const对象到const的转换。
  • 允许从派生类型到基类类型的转换。
  • 将数组转换为指向数组类型的指针,将函数转换为指向函数类型的指针。
4.异常的重新抛出

有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理。

class Exception{public:    Exception(int errId = 0, const char * errMsg = "")        : _errId(errId)        , _errMsg(errMsg)    {}    void What() const    {        cout << "errId:" << _errId << endl;        cout << "errMsg:" << _errMsg << endl;    }private:    int _errId; // 错误码    string _errMsg; // 错误消息};void Func1(){    throw string("Throw Func1 string");}void Func2(){    try    {        Func1();    }    catch (string & errMsg)    {        cout << errMsg << endl;        //Exception e (1, "Rethorw Exception");        //throw e ;        // throw;        // throw errMsg;    }}void Func3(){    try    {        Func2();    }    catch (Exception & e)    {        e.What();    }}
5.异常规范

在函数声明之后,列出该函数可能抛出异常类型,并保证该函数不会抛出其他类型的异常。

  • 成员函数在类内声明和类外定义两处必须有相同的异常规范。
  • 函数抛出一个没有被列在它异常规范中的异常时(且函数中抛出异常没有在函数内部进行处理),系统调用C++标准库中定义的函数unexpected().
  • 如果异常规范为throw(),则表示不得抛出任何异常,该函数不用放在try块中。
  • 派生类的虚函数的异常规范必须与基类虚函数的异常规范一样或更严格(是基类虚函数的异常的子集)。因为:派生类的虚函数被指向基类类型的指针调用时,保证不会违背基类成员函数的异常规范。

exception 异常层次