RAII详解

 
 RAII(Resource acquisition is initialization) 资源获取即初始化 , 它是一项很简单的技术 , 利用 C++ 对象生命周期的概念来控制程序的资源 , 例如内存 , 文件句柄 , 网络连接以及审计追踪 (audit trail) .RAII 的基本技术原理很简单 . 若希望保持对某个重要资源的跟踪 , 那么创建一个对象 , 并将资源的生命周期和对象的生命周期相关联 . 如此一来 , 就可以利用 C++ 复杂老练的对象管理设施来管理资源 . 最简单的 RAII 形式是 , 创建这样一个对象 : 其构造函数获取一份资源 , 而析构函数则释放这份资源 :
Class Resource {……};
Class ResourceHandle {
Public:
Explicit Resourcehandle (Resource * aResource ):r_( aResource ) // 获取资源
~ ResourceHandle() {delete r_;} // 释放资源
Resource *get()    {return r_ ;} // 访问资源
   Private:
     ResourceHandle (const ResourceHandle &);
ResourceHandle & operator = (const ResourceHandle &);
     Resource * r_;
}
 
ResourceHandle 对象的最美妙之处在于 , 若它被声明为一个函数的局部变量 , 或作为函数的参数 , 或是一个静态对象 , 我们都可以保证析构函数会得到调用 , 从而释放对象所引用的资源 . 当面对草率的维护或传播的异常时 , 若希望保持对某项重要资源的跟踪 , 此为一个可利用的重要的属性 . 考虑以下未使用 RAII 的简单代码 :
Void f  ( ) 
{
     Resource *rh = new Resource;
     //…
    If( iFeelLikeIt () ) // 糟糕的维护
               Return;
   //…
     g(); // 抛出异常 ?
   Delete rh; // 一定能执行到这里吗 ?
}
 
  也许这个函数最初的版本是异常安全的 ,rh 所引用的资源总是可以得到释放 . 然而 , 随着时间的推移 , 当一些欠缺谨严的维护人员往代码里添加一些可能回提前返回代码,调用可能回抛出异常的函数 , 或者插入其他一些东西从而使得函数末尾的资源释放代码得不到执行的时候 , 这样的代码就不再是异常安全的了 . 使用 RAII, 可以使函数变得更简单 , 并且更强健 :
Void f ( )
{
     ResourceHandle rh ( new Resource );
 //…
     If( iFeelLikeIt() ) // 没有问题
               Return ;
 //…
   g ( ); // 抛出异常吗 ? 没有影响
//rh 析构函数执行 delete 操作
}
 
  当使用 RAII , 只有一种情况无法确保析构函数得到调用 , 就是当 ResourceHandle 对象被分配到堆上时 , 因为这样一来 , 只有显示地 delete ResourceHandle 对象 , ResourceHandle 对象所包含的对象的析构函数才回得到调用 ( 其实还有一些边缘性情形 , 着包括调用 abort exit 的情形 , 以及若抛出的异常从未捕获而导致不确定的情形 .):
ResourceHandle * rhp = new ResourceHandle( new Resource ); // 糟糕的注意 !
 
RAII 技术在 C++ 编程中影响是如此深远 , 以至于很难发现有哪个库组件或大型代码未以某种风格使用它们 . 注意 : 可以通过 RAII 进行控制的资源 , 范围非常广泛 . 除了控制本质上以为内存的资源 ( 包括缓冲区 , 字符串 , 容器实现等诸如此类的东西 ) , 还可以使用 RAII 来控制系统资源 , 包括文件句柄 , 信号量 (semaphore) 以及网络连接等 , 另外还包括一些其他的东西 , 例如登陆会话 (login session), 绘图形状等等 .
 
考虑下面的类 :
Class Trace
{
     Public:
               Trace( const char *msg ) : msg_(msg)
{ std::cout << “Entering ” << msg_ <<std::endl ;}
               ~Trace()
                            { std::cout << “leaving ” << msg_ <<std::endl ;}
     Private:
                   Std::string msg_;
};
 
Trace 这个例子中 , 控制的资源是一个准备打印的消息 ( 当离开一个作用区域时 ). 通过在不同类型的控制流程下监视起生命期来观察不同的 Trace 对象 ( 包括自动的 , 静态的 , 局部的以及全局的等等 ) 的行为 , 很有教育意义 .
Void f ( )
{
           Trace trace (“f”); // 打印 ”Entering” 消息
           ResourceHandle rh (new Resource ); // 获取资源
           //…
           If(iFeelLikeIt () )
                    Return ;
           //…
           g();
           //rh 析构函数执行 delete 操作
           //Tracer 析构函数打印 ”Leaving” 消息
}
  以上代码还展示了关于构造函数和析构函数结构激活的一个重要的定式 : 这些激活形成一个栈 , 确切地说 , 先于 rh 声明并初始化 tracer, 这样就保证 rh 将于 tracer 之前被销毁 ( 即后初始化的先销毁 ). 推而广之 , 无论何时我们声明了一系列的对象 , 这些对象在运行期将会以特定的顺序被初始化 , 并且最终以相反的顺序被销毁 . 这个析构顺序不会发生变化 , 即使碰到了意外的 return, 传播的异常 , 不寻常的 switch 甚至 goto, 均是如此 . 这个性质对于资源的获取和释放尤其重要 , 因为通常资源必须以特定的顺序进行获取且以相反的顺序进行释放 . 比如 , 在发送一个审计消息之前 , 必须打开一个网络连接 , 而在该网络连接被关闭之前 , 必须发出一个关闭审计消息 (closing audit message).
这种基于栈的行为甚至延伸到了个体对象的初始化和析构方面 . 一个对象的析构函数按照其基类在继承列表中声明的顺序来初始化各个数据成员 , 然后执行构造函数的本体 .
 
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值