最近在看auto_ptr 源码的时候,发现里面的异常说明很多。事实上对于exception handling这块,以前也有很多困惑的地方,只是由于平时代码中很少用到,于是就从来没仔细钻研过。本来这篇是用来写smart pointer的,既然遇到了exception handling这块,那么先把这块硬骨头啃下来再说吧。
翻阅了很多大师的经典著作,发现exception handling在《c++ primer》中只是概念性的提了下,对于技巧型的内容几乎没有涉及到;《effective c++》中只有一个条款中提及,《inside c++ object model》也提到很少,幸运的是《more effective c++》中却有一个专题来研讨这块,而且讲到很多技巧性的内容,令人脍炙人口。。 虽然新公司里很少用到exception handling,所有的状态信息都是以日志文件形式来记录,而谁也不能保证以后的工作中不会用到,对于一般性的异常处理,自认为还是可以应付的来的,至少不会导致因为没有处理的exception而teminate了当前程序,而如果要写出高质量高稳定性的C++代码,不掌握exception handling的技巧性使用应该是很难的(至少我是这么认为的),当然了,C阵营中错误代码或返回状态信息是另一类exception技能了。而正如Scott Meyers在《effective c++》的条款一所说:视C++为一个语言联邦;因而不该因为C++是由C发展而来而全盘忽视了C++的特性。。。
VS编译器对异常规范的忽视
异常规范在《C++ Primer》中倒是提及的多一些。这一块也是exception handling中最令人头大的一块,因为尽管编译器能检测出少量异常规范,对于大多数的异常规范,只有在运行期才能知晓,如果违反了异常规范,诸多大师的一致解释是:程序会自动调用标准库的unexpacted函数,此函数又调用teminate函数,从而直接终止程序的运行。来看看如下代码:
1. #include
2.
3. using namespace std;
4.
5.
6. typedef void (*CallBackPtr)(int nEventLocationX,int nEventLocationY,void *pDataToPassValue)throw();
7.
8. class CallBack
9. {
10. public:
11. CallBack(CallBackPtr fPtr,void *pDataToPassValue):func(fPtr),pData(pDataToPassValue)
12. {}
13. void MakeCallBack(int nEventLocationX,int nEventLocationY) const throw()
14. {
15. func(nEventLocationX,nEventLocationY,pData);
16. };
17.
18. private:
19. CallBackPtr func;
20. void *pData;
21. };
22.
23. void MyFunc(int nEventLocationX,int nEventLocationY,void *pDataToPassValue) throw(runtime_error)
24. {
25. cout<
27. }
28.
29. int main(int *argc , char **argv)
30. {
31. CallBackPtr Func = MyFunc;
32.
33. CallBack MyCallBack(Func,NULL);
35. return 0;
36. }
这是一个回调函数管理类的例子,是《more effective c++》的原例,如果按照Lippman在《C++ Primer》中所说的话,此程序应该不能通过编译,因为它存在一个编译期就能检测出来的违反异常规范的地方:对于函数MyFunc和函数定义CallBackPtr异常声明,MyFunc的规范更为严格,它应该不能转化为CallBackPtr的对象才是,因为CallBackPtr的异常规范为throw(),意味着此函数不会抛出任何异常。。而我在VS2008下却能正常编译通过,只有一个warning:C++ exception specification ignored except to indicate a function is not __declspec(nothrow),MSDN中对其解释是:“使用异常规范声明函数,Visual C++ 接受但并不实现此规范。包含在编译期间被忽略的异常规范的代码可能需要重新编译和链接,以便在支持异常规范的未来版本中重用。”意即VC编译器不支持异常规格说明。。 令我牵肠挂肚的是:倘若一直如此的话,那么C++的异常规范特性得全部由程序员来掌控,感叹VC编译器对于这点多少有些不人道。。 但我也不想因此而以点盖面的全盘否定VC编译器在其它方面的高性能。
使用smart pointer来防止destructor中的资源泄露
对于由于异常处理不当而引发的资源泄露,无疑亦是程序员喜欢讨论的话题之一,因为抛出异常意味着一个抛异常的代码块可能只执行了一部分(前提是当前函数没有处理异常),这样的话,那么异常又会传送到当前代码块的外围去处理,而引发资源泄露的代码块往往却是这块没被执行的代码,看看如下例子:
1. #include
2. using namespace std;
3.
4. class BaseClass
5. {
6. public:
7. BaseClass(){};
8. ~BaseClass(){};
9. };
10.
11. void ExceptionFunc() throw(runtime_error)
12. {
13. throw runtime_error("example exception handling!");
14. }
15.
16. void Function() throw(runtime_error)
17. {
18. BaseClass *pBase = new BaseClass;
19.
20. ExceptionFunc();
21.
22. delete pBase;
23. }
24.
25. int main(int *argc , char **argv)
26. {
27. try
28. {
29. Function();
30. }
31. catch(run