C++异常和Windows结构异常的比较
Windows结构异常有如下几个特性:
1、它使用__try、__except、__finally和__leave关键字和RaiseException API;
2、它由Windows所支持,因此它不适合其它操作系统
3、它不处理C++对象的解析
说明:在使用Windows结构异常的函数内,如果有C++对象,编译器会发
出:error C2712: Cannot use __try in functions that require object unwinding 的错误。
如:
void fun( )
{
CObject object ; // 错误!结构化异常无法解析C++ 对象
__try{
...
}
__except(...){
...
}
}
4、它用为硬件异常(例如访问非法或被零除)或操作系统异常的结果被抛出。
也可以作为RaiseException函数的结果被抛出;
而C++异常的特点是:
1、使用try、throw和catch等关键字;
2、它处理C++对象的解析;
3、它作为throw语句的结果被抛出;
注:MFC提供了第三种异常处理机制。它使用几个异常处理宏,这些宏现在也被编译成C++异常,因此没有必要在新的代码中使用它们!在MFC编程中,异
常对象都是从CExcept派生。大多数MFC异常处理对象都是动态分配的,因此当它们被捕获时,必须被删除;而没有捕获的MFC异常由MFC本身在AfxCa-
llWndProc函数中捕获并删除。
因为C++异常不能处理硬件和操作系统异常,因此需要将结构异常转化为
C++异常。
如何将结构异常转化为C++异常
Visual C++ 允许你通过使用_set_se_translator函数将结构异常转化为C++异常。
#include <eh.h>
class CSEHException
{
public :
CSEHException( UINT code , PEXCEPTION_POINTERS pep )
{
m_exceptionCode = code ;
m_exceptionRecord = *pep->ExceptionRecord ;
m_context = *pep->ContextRecord ;
ASSERTE( m_exceptionCode == m_exceptionRecord.ExceptionCode );
}
operator unsigned int() { return m_exceptionCode ; }
UINT m_exceptionCode ;
EXCEPTION_RECORD m_exceptionRecord ;
CONTEXT m_context ;
} ;
// 结构化异常到C++异常转化器
void cdecl TranslateSEHtoCE( UINT code , PEXCEPTIONPOINTERS pep )
{
throw CSEHException( code , pep ) ;
}
int APIENTRY WinMain( ……)
{
// 安装异常转化器
setse_translator( TranslateSEHtoCE ) ;
}
注意,异常转化器在每个线程的基础上进行工作,因此你需要为每一个线程安装一个转化器。这种异常转化器有一个副作用,你会在输出窗口的Debug标签中看见两个异常跟踪消息:一个是为了原来的结构异常,另一个是为了转化后的C++异常。使用了异常转化器后,你就可以像捕获C++异常一样捕获结构异常。
异常相关的编译选项
先看下面的函数:
void TestBugException()
{
try{
int *pInt = 0 ;
*pInt = 42 ;
}
catch(…){
MessageBox(0,””,0,MB_OK);}}
显然,上面的代码将会产生一个结构化异常,而处理异常却是用C++的异常
机制,这个异常会不会被处理呢?答案是决定于编译环境,在VC的默认情况
下,在调试版本中处理异常,而在发布版中并不进行处理。对于VC而言,
C++异常只能由throw语句抛出,或者在调用的函数内有throw语句。任何其它
的代码都不能接收C++异常,因此调式器将认为在try语句内没有C++异常(
因为没有throw语句),因而在发布版里将不需要的异常处理代码除去,以使程序得到优化。
上面描述的异常被称为同步异常模型,由/GX编译选项设置,而且是VC6的默认设置。与之相对的是异步异常模型,其采取任何指令都会产生
异常的机制,异步异常模型由/EHA编译选项设置。即/EHA选项可以使C++异
常机制捕获结构化异常。
还有一个编译选项:/EHS,也是同步异常,但和/GX不同的是,/GX表示
假设 extern “C”的函数不会抛出C++异常。extern “C”函数一般是由C函数编写的,因此它们不会抛出C++异常,当然也有例外。
如果你的程序使用了异常机制,但没使用其中一个异常处理编译选项,编译时将会产生一个警告信息:warning C4530 。
让new 和malloc 抛出异常
在VC默认情况下,new和malloc对于错误不会抛出异常。你可以通过使用
_set_new_handler安装一个处理器,让new针对错误抛出异常。通过调用
_set_new_mode让malloc使用同一个处理器。
#include <new.h>
int NewHandler( size_t size )
{
throw exception(“xxxx”);
}
int main()
{
_set_new_handler( NewHandler ) ;
_set_new_mode(1);
}
_set_new_mode(1) 表示malloc要使用new处理器。
异常的性能
当抛出异常时,函数调用链将从此回溯搜索,寻找可以处理抛出的这类异常
的处理器。当然,如果没有找到合适的处理器,进程将结束。如果处理器没有找
到,调用栈将被释放,所有的自动(局部)变量也将释放,然后栈将被整理为异
常处理器的上下文相关设备。因此异常开销由维护一个异常处理器目录和一个活动的自动变量表(它需要额外的代码、内存,而且不论异常是否抛出,都会运行),
还得加上函数调用链的搜索、自动变量的解析和栈的调整(它只在抛出异常的时候需要执行)组成的。
在很少抛出异常的情况下使用异常的开销并不大,而且可能会提高性能。
看下面的代码:
// 没有使用异常,而是判断指针
int check( int *pInt1 , int *pInt2 , int *pInt3 )
{
if( pInt1!= 0 && pInt2 != 0 && pInt3 != 0 )
{
......
}
else
return –1 ;
}
// 使用异常,如果参数为NULL的几率很低 , 使用异常效率更高!
int check( int *pInt1 , int *pInt2 , int *pInt3 )
{
try{
...
}
catch(...)
{
return –1 ;
}
}
异常处理需要大量的额外操作,使得它并不适于经常运行的代码(比如循环中)。
更详细地说,catch块有一些开销,但是try块有很少的开销;因此只有在抛出异
常的时候才会有很多的异常操作开销。
什么时候抛出异常
首先说明一下什么时候适合使用返回值,
1、用于非错误的状态信息
2、用于大多数情况下可以随意忽略而不会出现问题的错误
3、用于更易出现在循环中的错误,因为异常的额外开销,所以为了更好的性能 ,使用返回值是一个更好的选择
4、用于中间语言模块中的错误,例如COM组件,必须使用返回值而不是异常。
抛出异常的时机应该是一个函数发现一个错误,这个错误能阻止程序正常的运行。当函数不能使用返回值时,如构造函数或重载的操作符‘=’等也要抛出异常。但是,应注意不能从析构函数中抛出异常,因为异常可以阻止delete的调用,这样就会有资源泄露:
delete pObject
相当于:
pObject->~CObject()
operator delete ( pObject )
因此析构函数中抛出异常会跳过operator delete的调用!
使用异常规范
处理异常的一问题是知道一个函数可以抛出哪一种类型的异常。函数抛出
的异常应该被认为接口协议的一部分,和它的参数和返回值一样。
C++提供了异常规范,看下面的函数原型:
Void NormalFunction() ;
Void NoThrowFunction() throw() ;
Void ThrowFunction() throw( Cexception ) ;
其中,NormalFunction 可以抛出任何异常,因为它没有异常规范,而NoThrow-
Function则不能抛出异常,这两个函数的异常规范看起来是截然相反的。最后,
ThrowFunction可以抛出任何从Cexption派生来的异常对象。
异常规范的问题:
1、异常规范不完整:如果抛出的异常不是从异常规范类派生来的,线程会调用unexpected , 它会默认地结束进程。为了创建一个完整的函数规范,你需要找到所有由这个函数直接抛出和所有这个函数调用的函数抛出的未处理的异常(如果这个函数内已处理了这些异常就不考虑)。
2、异常规范在编译时不会检查,如下面的代码在编译时不会出现警告:
void Funtion() throw() // 规范:不会抛出异常
{
throw CException ; // 但实际上有抛出异常
}
3、不能和模板混合,因为模板参数可以是任何类型的,你不能知道这种类型的成员函数可以抛出什么异常;
4、如果你使用异常异常(即catch可以捕获结构化异常),实际上任何函数都可以抛出一个转化后的结构异常。
1、非MFC的C++异常应该通过引用来捕获( catch( except &e ) )。使用引用捕
获异常不需要删除异常对象(因为使用引用的捕获的异常会在栈中传送), 而且它保留了多态性(因此你捕获的异常对象正是你抛出的异常对象)。使用指针捕获异常需要你删除对象。
2、MFC异常应该通过指针来捕获。因为它们从堆中分配,当你处理完异常之后,
你需要调用Delete成员函数,如下所示:
catch( CfileException *e )
{
e->Delete(); // 不要使用delete ,因为一些MFC异常作为静态对象创建
}
你不能使用省略捕获处理器捕获MFC异常,因为这会导致一个内存泄露