在开发C++程序时经常需要编写异常处理代码,异常处理机制的优点是可以让低层的错误沿着调用堆栈往上传递直至其被捕获并被处理,其缺点在于try/catch代码块的引用导致代码结构复杂,处理流程变得不清晰,代码的维护成本也增加了,Generic<Programming>: Change the Way You Write Exception-Safe Code Forever 介绍了一种巧妙的方式来摆脱try/catch的禁锢,让代码读更加简洁、优雅。
本文不再赘述原文中的解决方法,重点介绍一下其实现机制。
2 * ScopeGuardImplBase基类负责管理dismissed_标记,dismissed_的值决定了派生类析构时是否需要调用处理函数进行清理
3 */
4 class ScopeGuardImplBase {
5 public :
6 void Dismiss() const throw () {
7 dismissed_ = true ;
8 }
9
10 protected :
11 ScopeGuardImplBase() : dismissed_( false ) {}
12
13 ScopeGuardImplBase( const ScopeGuardImplBase & other) : dismissed_(other.dismissed_) {
14 other.Dismiss();
15 }
16
17 // 没有使用虚析构函数
18 ~ ScopeGuardImplBase() {}
19
20 mutable bool dismissed_;
21
22 private :
23 // 不允许赋值操作
24 ScopeGuardImplBase & operator = ( const ScopeGuardImplBase & );
25 };
26
27
28 /* *
29 * ScopeGuardImpl1派生类实现参数个数为1的处理函数调用
30 */
31 template < typename Fun, typename Parm >
32 class ScopeGuardImpl1 : public ScopeGuardImplBase {
33 public :
34 ScopeGuardImpl1( const Fun & fun, const Parm & parm)
35 : fun_(fun), parm_(parm)
36 {}
37
38 // 对象析构时自动调用处理函数
39 ~ ScopeGuardImpl1() {
40 if ( ! dismissed_) fun_(parm_);
41 }
42
43 private :
44 Fun fun_;
45 const Parm parm_;
46 };
47
48 /* *
49 * ScopeGuardImpl1的helper函数
50 */
51 template < typename Fun, typename Parm >
52 ScopeGuardImpl1 < Fun, Parm > MakeGuard( const Fun & fun, const Parm & parm) {
53 return ScopeGuardImpl1 < Fun, Parm > (fun, parm);
54 }
55
56 /* *
57 * ScopeGuard为ScopeGuardImplBase的常引用类型
58 */
59 typedef const ScopeGuardImplBase & ScopeGuard;
从上面的代码中可以看出基类ScopeGuardImplBase并不是通过虚析构函数来实现多态,而是通过将ScopeGuard定义为ScopeGuardImplBase的常引用类型,其技巧是依据C++标准:使用临时变量来初始化引用,该临时变量将一直存在直至引用的生命周期结束。
下面通过一个例子来说明:
ScopeGuard closeIt = MakeGuard(std::fclose, topSecret);
上面的代码中只提供了接受1个参数的ScopeGuardImpl1对象类型,只要依葫芦画瓢就很容易实现接受0个、2个或者更多个参数的派生类型(ScopeGuardImpl0, ScopeGuardImpl2...);同时还需要重载helper函数MakeGuard来支持不同的参数个数实现版本。
原文中还提供了使用对象成员函数作为处理函数的版本,实现原理和上面普通函数版本一致。
boot版本
熟悉boost的同学会发现借助boost::shared_ptr和boost::bind就可实现这个ScopeGuard工具,详见:Using shared_ptr to execute code on block exit