C++华丽的exception handling(异常处理)背后隐藏的阴暗面及其处理方法 (二)

2014-11-24 12:22:32 · 作者: · 浏览: 2
time_error &err)
32. {
33. cout< 34. }
35. return 0;
36. }

不得不承认在main函数返回之前,所有的异常确实得到了处理,让人难以忽视的是:Function里面抛出异常后,delete pBase没有执行,这就意味着发生了内存泄露。。指针无处不在,如果我们不想用指针而想提高代码的效率和质量,那几乎是不可能的。事实上,我们可以在Function函数里捕捉异常,然后在异常处理块中执行delete pBase,可以避免由此引发的内存泄露,然而这样做的缺陷是要写两个delete,Scott Meyers对于这种引发内存的更好的处理方式是:使用smart pointer。 如果将Function改为如下:
1. void Function() throw(runtime_error)
2. {
3. BaseClass *pBase = new BaseClass;
4. auto_ptr PtrBase(pBase);
5. ExceptionFunc();
6. }

用类来管理资源是防止资源泄露的有力法器之一,这种情况下异常抛出后,auto_ptr对象肯定会执行析构函数,此时会自动释放其指针成员指向的对象资源,即便它的对象为NULL,由于C++保证了delete空指针无异常的特性,所以资源是肯定会正确的释放。然而在我看来,smart pointer的使用也只是一种折中而已,因为使用auto_ptr而带来的负面性后果其实也可以大作讨论了,有待我之后的smart pointer文章再作详细讨论。
异常逃离destructor的灾难性后果
这一点也是唯一一条Sotte Meyyers在《effective c++》(条款8)和《more effective c++》(第五章节)中重复讨论了两次的条款,当destructor中无法处理异常的话,程序会直接调用teminate从而终止。。如果试图在destructor外部捕获异常,那将是徒劳的,正如一般重载delete运算符的声明式一样,往往在后面又加个异常规范throw(),这意味着delete外部根本无法捕捉到其内部的异常。看看下面这个简单例子:
1. #include
2. #include
3. using namespace std;
4. class BaseClass
5. {
6. public:
7. BaseClass(){};
8. ~BaseClass()
9. {
10. throw runtime_error("example runtime error.");
11. };
12. };
13.
14. int main(int *argc , char **argv)
15. {
16. BaseClass *pBase = new BaseClass;
17. delete pBase;
18. return 0;
19. }

在VS2008下调用teminate时候还会调用abort,这个程序会非正常结束,如果在main函数中试图这样做:
1. int main(int *argc , char **argv)
2. {
3. BaseClass *pBase = new BaseClass;
4. try
5. {
6. delete pBase;
7. }
8. catch(runtime_error &err)
9. {
10. cout< 11. }
12. return 0;
13. }

结果会跟上面一样(非正常结束),因为delete是不会将任何异常传递到其外面的;一种比较折中的解决方法是,当destructor中存在异常抛出时,在destructor最后添加一个能捕获所有异常的catch处理块,catch处理块又什么工作都不做,如下:

1. ~BaseClass()
2. {
3. try
4. {
5. throw runtime_error("error in destructor");
6. }
7. catch(...)
8. {
9. }
10. };
看起来是一种很坏很无奈的办法,但正如Scott Meyers在《effective c++》中所说:
“一般而言,将异常吞掉是个坏主意,因为它压制了"某些动作失败"的重要信息!然而有时候吞下异常也比负担"草率结束程序"或"不明确行为带来的风险好”。


后记
对于很多exception handling的概念性细节(比如何时使用引用类型的异常捕捉、异常捕获层次的类型转换等等)我没做任何阐述,可以去看看《C++ PRIMER》的第十七章,有着很想尽的讲解。。。 对于MS编译器对异常规范的不支持,我很难理解,因为G++编译器确实是支持的。之前在讨论C++的object布局时(点击这里)也曾感叹MS的编译器在优化方面没G++走得快,对于这些,或许是我运气不好,老是碰到MS不如G++的地方,也或许是我现在几乎不用G++编译器的而体会不到其不如MS编译器的地方的缘故吧。。。exception handling的确能为提高代码质量的改善作出或多说少的贡献,但华丽丽的外表下,因为用不好它而导致的程序的很多不明确(如teminate当前程序)和不正常(如资源泄露)行为也是令人比较头大的地方。貌似只有多熟用有技巧性的用是唯一能解决所有问题的方法了。。


本文出自 “酋长” 博客