翻译《有关编程、重构及其他的终极问题?》——8.记住:析构函数中的异常是危险的
来源:互联网 发布:excel数据有效性在那 编辑:程序博客网 时间:2024/05/18 01:28
翻译《有关编程、重构及其他的终极问题?》——8.记住:析构函数中的异常是危险的
标签(空格分隔): 翻译 技术 C/C++
作者:Andrey Karpov
翻译者:顾笑群 - Rafael Gu
校验者:高国栋
最后更新:2016年12月08日
本书背景说明、总目录等介绍,可以跳转到以下链接进行查看:
http://blog.csdn.net/headman/article/details/53045891欢迎大家转载,但请附上原作者以及翻译者的名字、原文出处,以尊重光荣的劳动者。
8.记住:析构函数中的异常是危险的
这次说的问题是在LibreOffice项目中发现的。这个错误的PVS-Studio诊断是这么描述的:V509 The ‘dynamic_cast < T&>’ operator should be located inside the try..catch block, as it could potentially generate an exception. Raising exception inside the destructor is illegal(译者注:大意是dynamic_cast
virtual ~LazyFieldmarkDeleter(){ dynamic_cast<Fieldmark&> (*m_pFieldmark.get()).ReleaseDoc(m_pDoc);}
解释
当程序中抛出异常时,对应的线程栈就开始回滚,其中的对象就会自动调用析构函数以便释放自己。如果一个对象的析构函数在线程栈回滚时被调用然后抛出异常,C++标准库就会即可调用terminate()函数终止整个程序。所有就有了一条规则:析构函数中永远不要抛出异常,即使有异常也要在析构函数内部处理掉。
前面引用的代码是非常危险的。如果转换一个对象到指定类型失败,dynamic_case操作符将抛出std::bad_cast异常。
同样,其他的构建抛出异常也是危险的。比如,在析构函数中分配内存就不怎么安全,因为如果一旦失败,就会抛出一个std:bad_alloc异常。
正确的代码
如果使用dynamic_cast时不用引用而是指针就可以修复前面的问题,这样,如果转换对象类型失败,就不会抛出一个异常,而会返回nullptr。
virtual ~LazyFieldmarkDeleter(){ auto p = dynamic_cast<Fieldmark*>m_pFieldmark.get(); if (p) p->ReleaseDoc(m_pDoc);}
建议
让你的析构函数越简单越好,不要在其中分配内存或读文件。
当然,你不可能一直让析构函数保持简单,但我相信我们可以尽力接近这个目标。另外,一个复杂的构造函数,意味着类结构设计的贫乏,以及有缺陷的构思。
在析构函数中如果代码越多,一旦发生问题,那么就难以确认问题,因为就很难去辨别那些代码抛出或者不抛出异常。
如果一旦有异常在析构函数中抛出,一个通常的好办法就是用catch(…)在析构函数中处理掉它:
virtual ~LazyFieldmarkDeleter(){ try { dynamic_cast<Fieldmark&> (*m_pFieldmark.get()).ReleaseDoc(m_pDoc); } catch (...) { assert(false); }}
当然,这样使用会隐藏析构函数中的一些错误,但是,通常来说,却能让你的应用程序更稳定。
我并不是坚持一定不能在析构函数中抛出异常——这其实依赖于实际的情况,有时候在析构函数中抛出异常还是很有用的,我在一些十分特别的类中见过这种情况,这些类一般被设计成在析构时抛出异常,但这非常很少见。如果是类似“圈绳”,“点”,“画刷”,“三角”,“文档”等的类(译者注:这里作者举例了画板类的应用中的对象),那么就不该在析构函数中抛出异常(译者注:因为用户还没有保存文档,程序就可能异常退出了,结果你懂得)。
只要记住,两次嵌套的异常抛出就会导致程序退出,所以最后由你决定这种情况是否该在项目中发生。
- 翻译《有关编程、重构及其他的终极问题?》——8.记住:析构函数中的异常是危险的
- 翻译《有关编程、重构及其他的终极问题?》——26.潜伏的VARIANT_BOOL
- 翻译《有关编程、重构及其他的终极问题?》——前言
- 翻译《有关编程、重构及其他的终极问题?》——17.使用专门的函数清除专有数据
- 翻译《有关编程、重构及其他的终极问题?》——7.不要在循环中调用alloca()函数
- 翻译《有关编程、重构及其他的终极问题?》——30.Visual C++和wprintf()函数
- 翻译《有关编程、重构及其他的终极问题?》——16.在编程过程中“装逼”是不可接受的
- 翻译《有关编程、重构及其他的终极问题?》——5.使用工具去分析你的代码
- 翻译《有关编程、重构及其他的终极问题?》——10.避免使用多个小的#ifdef块
- 翻译《有关编程、重构及其他的终极问题?》——13.表格化的格式化
- 翻译《有关编程、重构及其他的终极问题?》——23.自动获取字符串的长度
- 翻译《有关编程、重构及其他的终极问题?》——27.狡猾的BSTR字符串
- 翻译《有关编程、重构及其他的终极问题?》——19.如何合理的从一个构造函数中调用另外一个构造函数
- 翻译《有关编程、重构及其他的终极问题?》——9.使用'-0'符号作为结尾标记
- 翻译《有关编程、重构及其他的终极问题?》——22.不要使用#pragram warning(default-X)
- 电子书《有关编程、重构及其他的终极问题?》的翻译
- 翻译《有关编程、重构及其他的终极问题?》——28.如果你可以使用简单的函数就不要使用宏
- 翻译《有关编程、重构及其他的终极问题?》——21.正确的检查文件的结尾符(EOF)
- noip2016 Day2 T3:愤怒的小鸟 (状压DP+二进制压位)
- 用了freopen,却想恢复控制台输出的方法
- 三次握手,四次挥手
- [Web前端技术教学]网页布局-基础布局练习-带框的界面铺满整个浏览器
- 基于visual c++之windows核心编程代码分析(13)获取磁盘可用空间信息
- 翻译《有关编程、重构及其他的终极问题?》——8.记住:析构函数中的异常是危险的
- Atitit 查询优化器的流程attilax总结
- StudentManager-java+mysql学生管理系统
- 130. Surrounded Regions
- 博客开通
- 配置ssh无密码访问
- 基于visual c++之windows核心编程代码分析(8)自动下载更新程序
- C#调用C++的dll两种方法(托管与非托管)
- 自己实现简单RPC功能