有关C++异常安全的一点个人想法

原创 2002年11月15日 14:02:00

本人有幸于10月26日下午在清华大学的建筑报告馆聆听了C++之父Bjarne Stroustrup博士的讲座。精彩的演讲,引人入胜的内容,着实令人难忘。同时,我也亲身感受到了大师在回答场下听众问题时的平易近人和循循善诱。本次讲座的主题是Exception Safety,以下是我在听完讲座后,结合自己的一点切身体会,对C++ Exception Handling及相关内容的一点思考,如有考虑不周之处还请大家指正。

在C++语言中,资源管理(Managing Resources)始终是一个十分重要的话题,也是程序员在使用C++语言编写代码时需要十分注意的地方,稍有不慎就可能导致资源泄漏,在我以往的编程实践中就经常遇到此类问题。而“resource acquisition in initialization”是一种处理此类问题的较好方法,这是Stroustrup博士在演讲中所提到的。关于这一点,在博士所著的D&E以及相关论文中也有所提及。该方法使用一个类来代表对资源的管理逻辑,将指向资源的句柄(指针或引用)通过ctor传递给该类,在该类的实例被销毁时由dtor负责释放资源。可以在创建该类实例之前申请资源,也可以在构造时由该类的ctor负责申请资源。这种方式的基本思路是,不论exception是否发生,由于C++的语言机制保证了,一定会调用位于当前scope的对象的dtor,所以只要在dtor中加入资源回收的代码,那么这些代码总是会被执行的。这种方法的好处在于,由于将资源回收的逻辑通过单独的类从原有代码中剥离出来,使程序员总是不会遗漏,思路也变得清晰。

我觉得,“resource acquisition in initialization”技法,在处理有关exception的问题时,其适用范围还可以扩展。不单涉及资源管理,只要当scope里存在类似于fopen/fclose、new/delete这样的对称操作时,就可以酌情考虑采用这种方法。避免资源泄漏固然是头等大事,应该列于basic guarantee之内。但某些对称操作,如果会影响程序的正常执行甚至是产生fatal error的话,那么也是不可轻视的。而对于一个软件而言,杜绝fatal error应该也算是一个basic guarantee了。

以下是我在实践中遇到的一个例子。有意思的是,这个例子是本人在所负责的软件模块中首次决定使用exception handling所遇到的,可谓出师不利:)经过简化后的代码基本如下:
void f(C *pObj)
{
 pObj->Editable(true);
 // do some work with object
 pObj->Editable(false);
}
函数f的作用是对传入其scope的pObj所指对象进行某些操作。当最初引入exception handling时,代码改变如下:
void f(C *pObj)
{
 pObj->Editable(true);
 try {
  // do some work with object
  // may cause exception
 } catch(...)
 {
  // do some thing and rethrow
  throw;
 }
 pObj->Editable(false);
}
此处rethrow是为了使f的调用者能有机会做一些处理,这是在设计时所需要的。类似这样的做法在一般的exception处理程序中是很常见的,但是我的疏忽却另自己吃了大亏。虽然,从经过简化的代码中很容易看出破绽来,但是由于当时经验不足,加之程序逻辑复杂,直到测试时通过最终的GUI才发现了问题。经过几个小时的艰苦调试,最后发现问题出在f函数。事实上,函数f的行为隐含了一个assert,即:f保证不对pObj所指对象的不可编辑状态做出更改,在调用f前对象是不可编辑的,调用后仍然如此。而在上述程序中,当exception发生时,由于没有执行pObj->Editable(false)这一语句,所以导致程序最终出错,而且这一错误隐蔽在无数代码中,exception情况又并非每次都发生,使我在调试时定位错误花费了不少精力。
在找到了错误根源之后,我采用了如下的补救措施,这一做法被Stroustrup博士称为naive use:
void f(C *pObj)
{
 pObj->Editable(true);
 try {
  // do some work with object
  // may cause exception
 } catch(...)
 {
  // do some thing and rethrow
  pObj->Editable(false);
  throw;
 }
 pObj->Editable(false);
}
在写下这段代码的时候,直觉告诉自己,这里存在Bed Smell,但是由于时间紧迫,所以当时暂且容忍了这种Quick and Dirty的做法。正如Stroustrup博士在D&E中所指出的,这种做法的缺点是啰嗦,冗长乏味,而且可能代价昂贵。仔细分析一下,就可以看出这里存在的潜在危险:两处pObj->Editable(false)事实上是重复代码,我们需要始终保持两处代码的一致性,如果一段时间后,需要在pObj中增加一种类似Editable的属性,这种一致性的保持,就需要延续,很难保证不会再次疏忽。
于是,遵照大师的教诲,我增加了一个辅助类,代码如下:
class C_Handle {
 C* _pObj;
public:
 C_Handle(C* pObj) {
  _pObj = pObj;
  _pObj->Editable(true);
  // may be other operations
 }
 ~C_Handle() {
  _pObj->Editable(false);
  // also may be operations according to ctor
 }
 operator C* () { return _pObj; }
};
C_Handle的ctor和dtor中,对_pObj所指对象的操作是成对出现的,所以在以后扩展时也不容易出错。此时f函数的代码也变得简洁了许多:
void f(C* pObj)
{
 C_Handle ch(pObj);
 try {
  // do some work with object
  // may cause exception
 } catch(...)
 {
  // do some thing and rethrow
  throw;
 }
}

个人觉得,这种技法应该具有普遍意义。现总结如下:在某个scope内出现针对某个对象的若干对称操作,而在彼此对称的两组操作间可能抛出exception以破坏这种对称性,并且这种破坏将导致与该scope相关的某种assert为false时,就可以考虑使用类似于Stroustrup博士在处理资源管理问题时所推荐的这种“resource acquisition in initialization”技法。甚至可以认为,资源管理中发生的例子是这里所提到的情形的一个特例。在资源管理方面的另一个很典型的例子是Smart Pointer。

此外,对于这种方法可能存在的一个缺点是,或许会出现很多类似C_Handle这样的规模很小的辅助类。对此我们可以这样考虑:如果这些类不是很多,那么它们的存在将会给代码的编写和维护带来好处(想想前面提到的维护一致性的代价),并且如果程序中多处出现这样的类似情况时,这些类就可以复用了。而当类的数目多到让你无法容忍时,就该考虑一下其中某些类存在的必要性了,毕竟并非程序的每处都要使用exception handling,也许你的设计本身存在问题。此外,如果这些辅助类彼此有关联则可以考虑引入继承体系,而如果它们之间的行为及其相似,使用template机制进行泛化,也不失为一个优化策略。

个人的一点想法

javascript 解释性、用于客户端、就对象的脚本语言,能够控制html、css代码 1、编写位置 1)外部 新建一个后缀名为  *.js的文件,再在html文件中进行引用...
  • dfqwer123
  • dfqwer123
  • 2017年01月03日 16:30
  • 70

关于安全的一些反思 -- QQ被黑后的一点想法

引言 我从来不会想到我的密码组合会被别人知道,因为一直以来我在信息安全方面做了不少工作。而这个复杂的密码组合一直都很好地发挥了它们的作用。当我看到我的账号在我加的群里都发了一些不堪入目的文字的时候,...
  • stevenybw
  • stevenybw
  • 2013年12月08日 12:09
  • 1083

C++程序中如何规避子进程退出过程中出现的异常

linux下通过fork方式创建的子进程,在进程退出后
  • chengjian1027
  • chengjian1027
  • 2014年04月09日 23:59
  • 605

项目从想法到实现需要经历哪些过程

由于计算机的普及、软件的广泛使用,公众对于软件项目是个啥东西都有一个概念上的认识。很多人对于公司哪些地方将要应用一套软件系统,已经能够提出一定的想法。然而,在从想法到软件落地应用的过程中,软件项目要经...
  • lejuo
  • lejuo
  • 2014年07月22日 18:21
  • 7089

c++简易贪吃蛇

 http://blog.sina.com.cn/s/blog_641f90190100k6yo.html 模仿这个写的 基于dos 想要实现空格键暂停任意键开始(包括方向键) ...
  • CCCCCCGGGGGGGG
  • CCCCCCGGGGGGGG
  • 2015年05月23日 11:11
  • 416

Matlab编程的一点想法

我这菜鸟在大二上学期就学习了Matlab的课程,并且还顺带了10多个上机课时。大三、大四期间的信号与系统以及通信原理的实验也都是由Matlab完成的。研一的小论文的仿真也是用Matlab完成的,按理说...
  • ly1046906153
  • ly1046906153
  • 2014年03月19日 19:16
  • 579

对创业团队的一点想法

本人 没有强大的技术,没有广阔的人脉,没有超前的远见,只因在创业团队中待过一年,有了一些想法,即记录下来。这里对给我这次机会的公司表示感谢!这里说提互联网及软件方向的创业团队。1. 不宜过早制度化当然...
  • leishengsheng
  • leishengsheng
  • 2016年08月11日 10:27
  • 139

关于null的类型 的一点想法

一直不是特别清楚null,只知道它是'空'不存在,不是"",'',空集合等等。有点书上把java的类型分为三类(基本数据类型(int ,double),引用数据类型(对象)和null类型)。 nul...
  • qq_15099183
  • qq_15099183
  • 2017年06月12日 16:47
  • 46

管理的一点想法

今年下半年,由于一些特殊情况,由研发同时兼任项目负责人,从招聘到项目管理在到最后结项,中间的曲曲折折,似有一些心得,说与听一下。 1.如何留下有价值的成员?    这个也是我在工作中最常见到的,比...
  • dragonandlion
  • dragonandlion
  • 2015年09月16日 23:58
  • 214

关于“平衡”与“常识”的一点想法

起因,用热水刷碗的时候,看到哗哗的流水,思考到底要不要节水的问题; 联想到法西斯、纳粹当时宣扬的地球清理计划问题; 进一步联想到,是自己主动变化还是被动变好的问题。 大家一定好奇为什么这几个事情会...
  • coollangzi
  • coollangzi
  • 2017年03月25日 12:40
  • 577
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:有关C++异常安全的一点个人想法
举报原因:
原因补充:

(最多只允许输入30个字)