我们知道,对于c++、java等这些语言中,提供了这种设施来帮助我们捕获程序运行时的异常。
1: try {
2:
3: } catch(...) {
4:
5: }
可是在C++编程中,需要我们自己手动释放所获取的资源(内存,文件句柄等),如:
1: int *a = new int(0);
2: //do something
3: delete a;
如果说在//do something这个地方程序抛出了异常,那么就很可能造成我们前边动态申请的一块内存得不到释放而造成内存泄漏,所以我们就需要对这部分的代码加入异常处理,利用try/catch结构捕获程序运行期产生的异常来提高程序的健壮性。
如果在一个程序中,我们有很多地方需要手动申请/释放资源,那么我们就需要在每个地方都用try/catch去捕获异常,这样不仅影响代码整体的美感,同时对效率、可读性都会带来影响。这里我介绍一种利用ScopeGuard来编写异常安全代码的方式。
实际上,ScopeGuard这个设施在2000年就在DDJ杂志上的中被提出来。
1: class ScopeGuardImplBase
2: {
3: ScopeGuardImplBase& operator =(const ScopeGuardImplBase&);
4: protected:
5: ~ScopeGuardImplBase()
6: {}
7: ScopeGuardImplBase(const ScopeGuardImplBase& other) throw()
8: : dismissed_(other.dismissed_)
9: {
10: other.Dismiss();
11: }
12: template
13: static void SafeExecute(J& j) throw()
14: {
15: if (!j.dismissed_)
16: try
17: {
18: j.Execute();
19: }
20: catch(...)
21: {}
22: }
23: mutable bool dismissed_;
24: public:
25: ScopeGuardImplBase() throw() : dismissed_(false)
26: {}
27: void Dismiss() const throw()
28: {
29: dismissed_ = true;
30: }
31: };
32: typedef const ScopeGuardImplBase& ScopeGuard;
这个类是所有具体scope_guard实现的基类,dismissed_属性用于判断是否禁止执行执行清理工作;ScopeGuard为一个常引用,c++标准允许使用一个临时变量来初始化常引用,同时当用这类临时变量初始化一个常引用时,在引用指向他们期间这些临时对象将一直存在。
他下边定义了一系列的实现:
(F fun) 接受一个函数作为参数,在ScopeGuardImpl0析构的时候会根据dismissed_判断是否需要调用fun函数。(F fun, P1 p1) 接受两个参数,p1为执行fun时给它传递的参数(F fun, P1 p1, P2 p2) p1 p2为需要给fun传递的参数……另外还有一类是:ObjScopeGuardImpl0MakeObjGuard(Obj& obj, MemFun memFun) obj为一个对象,memFun为他的成员函数,在被析构的时候,会调用obj.memFun这个成员函数。 ObjScopeGuardImpl1MakeObjGuard(Obj& obj, MemFun memFun, P1 p1) 可以给memFun传递一个参数p1。 ……
然后,我们可以通过他定义的两个宏,来使用他们:
1: #define LOKI_ON_BLOCK_EXIT ::Loki::ScopeGuard LOKI_ANONYMOUS_VARIABLE(scopeGuard) = ::Loki::MakeGuard
2: #define LOKI_ON_BLOCK_EXIT_OBJ ::Loki::ScopeGuard LOKI_ANONYMOUS_VARIABLE(scopeGuard) = ::Loki::MakeObjGuard
请看下面的代码:
1: {
2: A a = DynamicCreateObjA()//动态分配内存,创建A的一个实例a
3: LOKI_ON_BLOCK_EXIT(DestoryObjA, a) //DestoryObjA为释放a对象所占用资源的函数
4: //do something
5: }
这样,我们不仅不需要去判断需要在哪里释放内存,同时,即便是在//do something这段代码运行时异常推出了,也是能保证a所申请的资源能得到释放的,这是为什么呢?
首先我们把这段宏展开,为:
1: ::Loki::ScopeGuard scopeGuard815 = ::Loki::MakeGuard( DestoryObjA , a );
实际是一个const引用指向一个临时对象,并且这个const引用(scopeGuard815,815是程序的行数,为了避免命名麻烦,由AC_ANONYMOUS_VARIABLE这个宏生成)是在栈上的,当scopeGuard815生命周期结束的时候,就会回调DestoryObjA来释放资源了。
另一种情况,我们需要调用a对象的一个成员函数来控制释放过程,则代码可以这样写:
1: {
2: A a = DynamicCreateObjA();
3: LOKI_ON_BLOCK_EXIT_OBJ( a , &A::Release );
4: //do something
5: }
如果需要在最后禁止通过ScopeGuard去释放资源,则就不能通过宏定义了,需要自己调用上边的MakeGuard函数:1: {
2: A a = DynamicCreateObjA();
3: ::Loki::ScopeGuard aGuard = ::Loki::MakeGuard( a, &A::Release() );
4: //do something
5: aGuard.Dismiss();
6: }