要防止因为异常产生的内存泄漏,可以使用智能指针,也可以用
__try
{
}
__finally
{
}
《Windows核心编程》一书第23~25章是很好的参考资料。
----------------------------------------------------
try,catch,throw:
try包含你要防护的代码 ,称为防护块. 防护块如果出现异常,会自动生成异常对象并抛出.
catch捕捉特定的异常,并在其中进行适当处理.
throw可以直接抛出/产生异常,导致控制流程转到catch块.
重要观点: C++中异常是用对象来表示的,称为异常对象.
基本格式:
try { your code; }
catch(T1 t1) //T1可以是任意类型,int,char, CException...
{ /*T1指定了你要捕捉的异常的类型,t1指定了异常 对象的名称,当有异常抛出,异常对象将被复制到t1 中,这样你就可以在本处理块中使用该对象,获取相关 信息,进行适当处理. 处理代码;*/}
catch(T2* pt1)
//上面的catch是值传递,这里使用指针传递.
{ 处理代码; }
catch(...)
//...是捕捉任意类型的异常.
{ 处理代码; }
//其他代码;
/*某个catch执行完,就跳转到这里继续执行. 在没有使用C++异常处理的情况下,如果在 此之前出现异常,则//这里的其他代码不会被执行 从而造成问题.请考虑在这里放置: delete pobj1; 如果不使用用try,catch机制,内存泄漏是必然的, 因为出现问题后,执行流程无法跳转到这里. */
/*说明: try{}之后可以跟任意个catch块. 发生异常后,会生成临时的异常对象,进行一些自动处理之后,程序 流程跳转到后面的catch(),逐个检查这些catch(),如果与catch() 中指定的类型一致,则将对象拷贝给catch参数中的对象, 接着执行该catch块中的代码,然后跳过其他所有剩下的catch, 继续执行后续的代码.
上面所说的自动处理指的是堆栈回退,说白了就是为函数中的局部对象调用析构函数,保证这些局部对象行为良好. */
catch()的顺序通常按照:从特殊到一般的顺序: catch(Tsub o){} catch(Tbase o){} catch(...){} 如果第一个catch为catch(Tbase){},则它将捕捉其所有派生类的 异常对象. 如果第一个catch为catch(...){},则其后的所有catch永远不可能 被执行.
重新抛出异常: 从上面的处理机制可以看到,只有一个catch可能被执行, 如果一个catch被执行,其他后续的catch就会被跳过了. 有时候一个catch中可能无法完成异常的全部处理,需要将 异常提交给更高的层,以期望得到处理.重新抛出异常实现 了这种可能性. 语法: throw ; 空的throw语句,只能在catch中使用. 它重新抛出异常对象,其外层的catch可能可以捕捉这个重新抛出的异常并做适当处理.
---------------------------------------------------------------------------------------------------------
1、基础介绍
try
{
//程序中抛出异常
throw value;
}
catch(valuetype v)
{
//例外处理程序段
}
语法小结:throw抛出值,catch接受,当然,throw必须在“try语句块”中才有效。
2、深入throw:
(i)、程序接受到throw语句后就会自动调用析构器,把该域(try后的括号内)对象clean up,然后再进
入catch语句(如果在循环体中就退出循环)。
这种机制会引起一些致命的错误,比如,当“类”有指针成员变量时(又是指针!),在 “类的构建器
”中的throw语句引起的退出,会导致这个指针所指向的对象没有被析构。这里很基础,就不深入了,提
示一下,把指针改为类就行了,比如模板类来代替指针,在模板类的内部设置一个析构函数。
(ii)、语句“throw;”抛出一个无法被捕获的异常,即使是catch(...)也不能捕捉到,这时进入终止函数
,见下catch。
3、深入catch:
一般的catch出现的形式是:
try{}
catch(except1&){}
catch(except2&){}
catch(...){} //接受所有异常
一般都写成引用(except1&),原因很简单,效率。
问题a:抛出异常,但是catch不到异常怎么办?(注意没有java 类似的finally语句)
在catch没有捕获到匹配的异常的时候,会调用默认的终止函数。可以调用set_terminate()来设置终止函数,参数是一个函数指针,类型是:void (*terminate)()。
到这里,可以题个问题:“没有try-catch,直接在程序中"throw;",会怎么样?”
其他一些技巧:
4、try一个函数体,形式如下
void fun(type1,type2) try----try放在函数体后
{
函数定义
}
catch(typeX){}
这个用法的效果就相当于:
void fun()
{
try{函数定义}
}
5、throw一个函数体,形式如下:
void fun (); // 能抛出任何类型的异常
void fun () throw(except1,except2,except3)
// 后面括号里面是一个异常参数表,本例中只能抛出这3中异常
void fun () throw() // 参数表为空,不能抛出异常
问题b:假设fun()中抛出了一个不在“异常参数表”中的异常,会怎么样?
答:调用set_terminate()中设定的终止函数。然而,这只是表面现象,实际上是调用默认的unexpected()函数,然而这个默认的 unexpected()调用了set_terminate()中设定的终止函数。可以用set_unexpected()来设置unexpected, 就像set_terminate()一样的用法,但是在设定了新的“unexpected()”之后,就不会再调用set_terminater中设定的 终止函数了。
这个语法是很有用的,因为在用别人的代码时,不知道哪个地方会调用什么函数又会抛出什么异常,用一个异常参数表在申明时限制一下,很实用。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
try{} catch(…){}
以前都是用try{} catch(…){}来捕获C++中一些意想不到的异常,今天看了Winhack的帖子才知道,这种方法在VC中其实是靠不住的。例如下面的代码:
- try
- {
- BYTE * pch ;
- pch = ( BYTE * ) 00001234 ; // 给予一个非法地址
- * pch = 6 ; // 对非法地址赋值,会造成Access Violation 异常
- }
- catch ( ... )
- {
- AfxMessageBox ( " catched " ) ;
- }
这段代码在debug下没有问题,异常会被捕获,会弹出”catched”的消息框。但在Release方式下如果选择了编译器代码优化选项,则 VC编译器会去搜索try块中的代码, 如果没有找到throw代码,他就会认为try catch结构是多余的, 给优化掉。这样造成在Release模式下,上述代码中的异常不能被捕获,从而迫使程序弹出错误提示框退出。
那么能否在release代码优化状态下捕获这个异常呢, 答案是有的。 就是__try, __except结构,上述代码如果改成如下代码异常即可捕获。
- __try
- {
- BYTE * pch ;
- pch = ( BYTE * ) 00001234 ; // 给予一个非法地址
- * pch = 6 ; // 对非法地址赋值,会造成Access Violation 异常
- }
- __except ( EXCEPTION_EXECUTE_HANDLER )
- {
- AfxMessageBox ( " catched " ) ;
- }
但是用__try, __except块还有问题, 就是这个不是C++标准, 而是Windows平台特有的扩展。而且如果在使用过程中涉及局部对象析构函数的调用,则会出现C2712 的编译错误。 那么还有没有别的办法呢?
当然有, 就是仍然使用C++标准的try{}catch(..){}, 但在编译命令行中加入 /EHa 的参数。这样VC编译器不会把try catch模块给优化掉了。
一篇比较好的英文文章谈这个问题: http://members.cox.net/doug_web/eh.htm