C++中的异常处理

C++中的异常处理

2011-02-12 09:38 by zhouli, 193 阅读, 0 评论, 收藏, 编辑

C++中的异常处理机制通常的异常抛出和处理主要使用下面这三个关键字:try\throw\catch.

一、处理形式

try

{

    //可能出现异常的代码块

}

catch(类型名 形参名) //捕获特定异常

{

    //特定异常处理

}

catch(...) //捕获所有的异常处理方法

{

    //所有异常处理

}

二、 异常的接口声明

为了加强程序的可读性,使函数的用户能够方便地知道所使用的函数会抛出哪些异常,可以在函数的声明中列出这个函数可能抛出的所有异常类型,例如:

void fun()  throw( A,B,C,D);

这表明函数fun()可能并且只可能抛出类型(A,B,C,D)及其子类型的异常。

如果在函数的声明中没有包括异常的接口声明,则此函数可以抛出任何类型的异常,例如:

void fun();


一个不会抛出任何类型异常的函数可以进行如下形式的声明:
 

void fun() thow();

      
三、异常处理中需要注意的问题

1. 如果抛出的异常一直没有函数捕获(catch),则会一直上传到c++运行系统那里,导致整个程序的终止

2. 一般在异常抛出后资源可以正常被释放,但注意如果在类的构造函数中抛出异常,系统是不会调用它的析构函数的,处理方法是:如果在构造函数中要抛出异常,则在抛出前要记得删除申请的资源。

3. 异常处理仅仅通过类型而不是通过值来匹配的,所以catch块的参数可以没有参数名称,只需要参数类型。

4. 函数原型中的异常说明要与实现中的异常说明一致,否则容易引起异常冲突。
 
5. 应该在throw语句后写上异常对象时,throw先通过Copy构造函数构造一个新对象,再把该新对象传递给 catch. 
       那么当异常抛出后新对象如何释放?
       异常处理机制保证:异常抛出的新对象并非创建在函数栈上,而是创建在专用的异常栈上,因此它才可以跨接多个函数而传递到上层,否则在栈清空的过程中就会被销毁。所有从try到throw语句之间构造起来的对象的析构函数将被自动调用。但如果一直上溯到main函数后还没有找到匹配的catch块,那么系统调用terminate()终止整个程序,这种情况下不能保证所有局部对象会被正确地销毁。
   
6. catch块的参数推荐采用地址传递而不是值传递,不仅可以提高效率,还可以利用对象的多态性。另外,派生类的异常扑获要放到父类异常扑获的前面,否则,派生类的异常无法被扑获。
   
7. 编写异常说明时,要确保派生类成员函数的异常说明和基类成员函数的异常说明一致,即派生类改写的虚函数的异常说明至少要和对应的基类虚函数的异常说明相同,甚至更加严格,更特殊。

 

四、runtime error的捕捉

VC++ Runtime Error,  对不少朋友来说, 这是一个十分讨厌的错误提示,  您可能不知道如何着手调试: 产生这个错误的原因是什么? 确实只有知道了产生这个错误的直接原因, 才能去调试这个错误.

     刚碰到这个错误的时候, 是发生在视频解码的时候,  由于解码一直在工作状态,  所以我也不知道如何去调试, 当出现这个错误之后, 我们大多数时候就忽略了, 想从其他地方解决, 提高稳定性, 甚至怀疑解码器的稳定性;  后来, 我接触解码库之后,  开始调试这样的错误, 刚开始这样的错误并不容易重现, 往往要几个小时,  当这个错误重现之后,  程序还是在运行的, 只是其中的某一个线程中断了执行,  其中的这个线程弹出了 "VC++ Runtime Error" 这样的对话框, 如果你点击它, 则整个应用程序会直接退出. 为了调试, 我就不能点击这个对话框, 而是使用VC2005附件到进程, 然后再直接中断进程, 这个时候, 会有一个线程中断点就在对话框的消息循环中, 仔细查看堆栈, 发现了一个函数: msvcrt.dll!_abort() ,  到这里是时候查看MSDN了:

     

      函数名: abort

  功 能: 异常终止一个进程

  用 法: void abort(void);

      In a single or multithreaded Windows-based application, abort calls the Windows MessageBox function to create a message box to display the message with an OK button. When the user clicks OK, the program aborts immediately. 

    

    我们的程序就是基于WINDOS窗口的多线程应用程序,  调用了abort就会弹出对话框, 在release版本中, 就是一个确认对话框, 点击后程序就提示出错并退出.

    在正常的程序里, 我们是不会调用abort的, 除非是遇到了严重的, 不能恢复的错误.  那么到底这个abort是怎么被调用的呢, 我们自己写的代码显然是没有这个函数, 再仔细查看堆栈,  发现是在一个C语言版本的开源库中.  我们的程序是需要7*24小时运行的, 出现了解码异常应该要被我们忽略, 而不是应用程序崩溃. 开源的跨平台解码库是C语言写的,  在出现了严重错误时, 就直接abort这也是可以理解的, 不过, 这样的程序在我们的代码中显然要避免.  大哥, 现在都是什么年代了, 很多程序都是需要一直跑的, 我只好改的库的源代码来重新编译程序才能解决这个问题了, 该怎么改了, 如果去分析解码的逻辑, 我们没有专业的人才.  我想就干脆从abort函数这里入手, 直接返回成功值, 但是这样对解码逻辑影响更大, 可能导致更大的错误,  我想到了操作系统的异常机制, 由于我们是在WINDOWS平台上工作, 所以可以利用WINDOWS结构化异常,  我们可取消abort调用, 在这里我们使用代码产生一个结构化异常(SEH), 结构化异常分为硬件异常和软件异常, CPU可以检查到内存非法访问和除零错误等异常, 那么我们就将abort替换成除零语句, 比如 int i = 10/0;

     当程序执行到这里的时候,  CPU会捕捉这个异常, 并提示用户, 我们可以在调用解码函数的地方, 增加SEH捕捉代码, 来捕捉这个错误, 那么程序就能忽略这个错误并继续执行了. 后来的事实也证明了这个错误的忽略对程序并没有什么明显的影响.  怎么写这个捕捉代码呢, 操作系统支持的SEH捕捉代码块为 __try - __finally 块和 __try - __except 块, 而__try - __finnaly块就可以实现我们的功能.  写到这里, 可能有朋友要说了, 我们平时见的最多的是try-catch语句,  那么我要解释一下了, try-catch 是C++异常的处理方式,  而__try-__finnaly是操作系统SEH异常处理方式.  在C++语言的try-catch并不能捕捉操作系统结构化异常(比如CPU异常, 内存访问冲突, 除零错误等). C++异常只能捕获软件异常, 通常是调用throw而产生的异常, 比如MFC异常中常见的CException.  

    SEH异常和C++异常有本质的区别, SEH是操作系统提供的异常处理技术, 在任何支持该操作系统的编程语言中, 都可以使用, 而C++异常处理只能在编写C++代码时使用。然而, 应当知道WINDOWS的VC++编译器是使用操作系统结构化异常来实现C++异常的.  也就是说, C++的try块在VC++下编译时, 会变成__try块,  C++的catch块会变成SEH的 __except块: catch测试则变成SEH异常过滤器, catch中的代码则变为__except中的代码. 事实上, C++的throw块, 在编译的时候也会变成SEH的RaiseException函数调用, 由c++异常变为SEH异常.

    __finnally的好处在于, 有时更详细的异常信息对我们没有更大帮助, 我们只需要捕获到异常并忽略它。上面提到C++异常在VC++里被转换成SEH异常, 那么在VC下使用try-catch是否能捕获硬件异常呢? 比如我们常见的 0x0000000C 不可读或写.  VC++编译器已经提供了支持:

      try {;}  catch(...){;}   这样的语句就能够捕获所有异常:包括CPU异常, 以及C++异常;  不过需要注意的是, 在VC6.0中, 是默认支持的. 但是在VC2005中,  是默认不捕获CPU异常的. 区别在于一个C++编译选项/Eha , 只有这个选项打开才能用上面的try-catch()捕捉SEH异常.

你可能感兴趣的:(C++中的异常处理)