alex

alex

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  7 随笔 :: 6 文章 :: 7 评论 :: 0 Trackbacks

今天来讲讲怎么编写异常安全的代码。
程序运行过程中,往往需要对一些流程加入异常处理,来提高程序的robust.比如
通过try catch来捕捉异常
try
{
    pMemory = new char[MAX_BUF_SIZE];
}
catch(std::bad_alloc& e)
{
    //error handling,er:do something resource free
}
但在程序代码段中出现大量的try catch,不仅从美观,效率和程序输写上都是不怎么好。
而另外一种对于异常的处理方法是依赖于c++的ctor/dctor的匹配来做的,就是所谓的
RAII,这个很容易让人联想到std::auto_ptr
std::auto_ptr<int> tmp(new int);
通过new分配的对象会在tmp生命结束后,释放相关的资源,通过这种方式,就能保证在程序异常,或退出时,已分配的对象能正确自动的释放拥有的资源,而在对象声明周期内,可以保证资源的有效性。
这种方式就是今天blog要写的主要内容,我们可以看到std::auto_ptr作用范围很小,只能对从堆上分配的对象进行管理,假如对文件打开句柄实行RAII,你也许会认为再写个不就是了,但这样只会造成源代码里充满了这些资源管理的类,这导致了一个严重的问题,好的结构在繁琐的流畅前面变的难堪。
那怎么样对这个进行泛化,从而能对比如从简单的指针释放,文件句柄维护,甚至相关的成员函数。我们来看下loki::socpeguard是怎么实现的:
先看下基本的用法
(1)  FILE* hFileOpen = fopen(....);
(2)  LOKI_ON_BLOCK_EXIT(flose,hFileOpen);
line2会在出LOKI_ON_BLOCK_EXIT域或程序异常结束时被调用,下面是对类成员的调用
void CTestObject::Create
{
    LOKI_ON_BLOCK_EXIT_OBJ(*this,FressResouce);
    ...
}
同上面差不多,会在这个函数结束后或异常结束后调用类成员的FreeResource.在正常流程结束后,可以通过调用Dismiss来防止对FreeResouce的调用,即类似数据库操作的commit操作。下面来分析下LOKI的实现:
从上面可以看到,RAII的是:
1:构造函数对资源的获取
2:稀构函数对资源的释放
先来看下LoKi对上面那2个宏的定义
#define LOKI_ON_BLOCK_EXIT      Loki::ScopeGuard LOKI_ANONYMOUS_VARIABLE(scopeGuard) = Loki::MakeGuard

#define LOKI_ON_BLOCK_EXIT_OBJ  Loki::ScopeGuard LOKI_ANONYMOUS_VARIABLE(scopeGuard) = Loki::MakeObjGuard
上面的Loki::ScopeGuard是一个基类的别名
typedef const ScopeGuardImplBase& ScopeGuard;
而LOKI_ANONYMOUS_VARIABLE(scopeGuard)用来我们产生唯一的名字,有时假如需要调用Dismiss的话,则需要自己去实现宏定义的内容,这样才能通过对象访问。Loki::MakeGuard或Loki::MakeObjGuard是用来产生对象的实际类型的,下面是一个
Loki::MakeGuard的例子:
template <typename F, typename P1>
inline ScopeGuardImpl1<F, P1> MakeGuard(F fun, P1 p1)
{
    return ScopeGuardImpl1<F, P1>::MakeGuard(fun, p1);
}
可以看到ScopeGuardImpl1<F, P1>是要产生的具体类型,MakeGuard通过函数参数的数目来重载的,而MakeGuard此处的作用是要睡死了...-_-'',作用是利用函数自动推导出参数的类型,这样就免去了指定ScopeGuardImpl1的类型的麻烦,而
ScopeGuardImpl1<F, P1>::MakeGuard(fun, p1);
简单的返回对象的一个临时变量,并assign给一个上面的一个scopeguard的实例,这里依赖一个C++的特性,临时变量的声命周期和通过他初始化的引用类型的声明周期是一致的。

从上面可以看到Loki定义了一个ScopeGuardImplBase的基础类。这个类定义了一个基本的方法Dismiss,以及相关的状态。下面是loki中这个类的定义
class ScopeGuardImplBase
{
    ScopeGuardImplBase& operator =(const ScopeGuardImplBase&);
 protected:
    ~ScopeGuardImplBase()
    {}
    ScopeGuardImplBase(const ScopeGuardImplBase& other) throw()
        : dismissed_(other.dismissed_)
    {
        other.Dismiss();
    }
    template <typename J>
    static void SafeExecute(J& j) throw()
    {
        if (!j.dismissed_)
           try
           {
              j.Execute();
           }
           catch(...)
           {}
     }
       
     mutable bool dismissed_;
public:
     ScopeGuardImplBase() throw() : dismissed_(false)
     {}
     void Dismiss() const throw()
     {
         dismissed_ = true;
      }
};
可以看到类里面定义了上面所说的一些属性,其中SafeExecute用来提供子类同一的资源释放方法,并调用子类的方法来具体操作,因为相关的函数,变量都保存在具体的子类,可以看到这个函数使用了try catch,这里加这个的目的是,因为资源释放要在子类的稀构里被触发,而调用具体的方法是外面传进来的,所以无法保证一定是异常安全的,而假如在稀构里面异常的话,会导致程序的行为无法定义。
下面具体来看下一个子类的实现:
template <typename F, typename P1>
class ScopeGuardImpl1 : public ScopeGuardImplBase
{
public:
    static ScopeGuardImpl1<F, P1> MakeGuard(F fun, P1 p1)
    {
        return ScopeGuardImpl1<F, P1>(fun, p1);
    }
    ~ScopeGuardImpl1() throw()
    {
        SafeExecute(*this);
    }
    void Execute()
    {
        fun_(p1_);
    }
protected:
    ScopeGuardImpl1(F fun, P1 p1) : fun_(fun), p1_(p1)
    {}
    F fun_;
    const P1 p1_;
};
在LoKi里面可以看到很多类似ScopeGuardImpl1的定义,比如ScopeGuardImpl0,
ScopeGuardImpl2,可以发现最后面的数字表示具体参数的数目。
可以看到上面所说的MakeGuard的定义,以及对基类方法的调用,可以看到构造函数接收的类型,一个函数对象,和一些参数对象,并保存,对于成员函数的scopeguard,LoKi定义了1些相似的类,主要是增加了对象的引用,还有就是函数的调用方式上。
上面可以看到参数是通过值的方式来保存的而不是通过引用。而且是const属性的,下面是相关的分析。
1:通过传值的方式,从而避免了异常抛出时,可能引用的对象被稀构
2:加const属性,从而保证了在func需要参数是reference时而保存的参数确是非const时产生相应的编译错误,因为对reference传人const non-reference形式是错误的。
而对于1的方式,存在的一种问题是假如操作的fun需要传入引用,那传进去的值就无法在释放的函数中被改变,而2是对这种的一种类似契约似的编程,Loki 提供的方法是通过一个中间对象来保存操作参数的引用,并赋予这个对象自动转换功能。下面是这个类的定义:
template <class T>
class RefToValue
{  
public:
    RefToValue(T& ref) : ref_(ref)
    {}
    RefToValue(const RefToValue& rhs) : ref_(rhs.ref_)
    {}
    operator T& () const
    {
        return ref_;
    }
private:
    // Disable - not implemented
    RefToValue();
    RefToValue& operator=(const RefToValue&);
       
    T& ref_;
};
可以很清楚的看到类的实现,下面是一个工具类
template <class T>
inline RefToValue<T> ByRef(T& t)
{
     return RefToValue<T>(t);
}
下面给个具体的例子,假如
template<typename _Ty>
void SAFEDELETE(_Ty*& ptr)
{
   if (NULL != ptr)
      delete ptr;
   ptr = NULL;
}

char* ptr = new char;
 
{
    LOKI_ON_BLOCK_EXIT(SAFEDELETE<char>,Loki::ByRef(ptr));
}
 
if (NULL == ptr)
  std::cout << "NULL" << std::endl;
基本上就这么多了,sleep去了
                                                    alex_yuu

posted on 2007-02-11 15:55 agerlis 阅读(1319) 评论(3)  编辑 收藏 引用

评论

# re: 编写异常安全的代码(loki::scopeguard) 2007-02-11 15:56 agerlis
请问,怎么设置背景色?  回复  更多评论
  

# re: 编写异常安全的代码(loki::scopeguard) 2007-02-12 16:13 mc
不错,把Loki::ScopeGuard介绍的比较透彻了:)  回复  更多评论
  

# re: 编写异常安全的代码(loki::scopeguard) 2007-05-28 16:40 candy
看到这些代码我就晕了,厉害啊,我比较讨厌编程。。嘿嘿,我是龚芳萍。  回复  更多评论
  


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理