C++健壮指针和资源管理

	我最喜欢的对资源的定义是:"任何在你的程序中获得并在此后释放的东西。内存是一个相当明显的资源的例子。它需要用new来获得,用delete来释放。同时也有许多其它类型的资源文件句柄、重要的片断、Windows中的GDI资源等等。很容易的可以将资源的概念推广到程序中创建、释放的所有对象,无论对象是在堆中分配的还是在栈中或者是在全局作用域内声明的。

对于给定的资源的拥有者,可以是负责释放资源的一个对象或者是一段代码。拥有者的所有权分为两种级别——自动的和显式的(automatic and explicit)。如果一个对象的释放是由语言本身的机制来保证的,这个对象的就是被自动地拥有者所有。例如,一个嵌入在其他对象中的对象,他的清除需要其他对象在清除的时候保证。外面的对象被看作嵌入类的所有者。   类似地,每个在栈上创建的对象(作为自动变量)的释放(破坏)是在控制流离开了对象被定义的作用域的时候保证的。这种情况下,作用域被看作是对象的所有者。注意所有的自动所有权都是和语言的其他机制相容的,包括异常。无论是如何退出作用域的——正常流程控制退出、一个break语句、一个return、一个goto、或者是一个throw——自动资源都可以被清除。
  到目前为止,一切都很好!问题是在引入指针、句柄和抽象的时候产生的。如果通过一个指针访问一个对象的话,比如对象在堆中分配,C++不自动地关注它的释放。程序员必须明确的用适当的程序方法来释放这些资源。比如说,如果一个对象是通过调用new来创建的,它需要用delete来回收。一个文件是用CreateFile(Win32 API)打开的,它需要用CloseHandle来关闭。用EnterCritialSection进入的临界区(Critical Section)需要LeaveCriticalSection退出,等等。一个"裸"指针,文件句柄,或者临界区状态没有所有者来确保它们的最终释放。基本的资源管理的前提就是确保每个资源都有他们的所有者。

第一条规则(RAII)

一个指针,一个句柄,一个临界区状态只有在我们将它们封装入对象的时候才会拥有所有者。这就是我们的第一规则:在构造函数中分配资源,在析构函数中释放资源。
  当你按照规则将所有资源封装的时候,你可以保证你的程序中没有任何的资源泄露。这点在封装对象(Encapsulating Object)在栈中建立或者嵌入在其他的对象中的时候非常明显。但是对那些动态申请的对象呢?不要急!任何动态申请的东西都被看作一种资源,并且要按照上面提到的方法进行封装。这一对象封装对象的链不得不在某个地方终止。它最终终止在最高级的所有者,自动的或者是静态的。这些分别是对离开作用域或者程序时释放资源的保证。
  下面是资源封装的一个经典例子。在一个多线程的应用程序中,线程之间共享对象的问题是通过用这样一个对象联系临界区来解决的。每一个需要访问共享资源的客户需要获得临界区。例如,这可能是Win32下临界区的实现方法。

class CritSect
{
 friend class Lock;
 public:
  CritSect () { InitializeCriticalSection (&_critSection); }
  ~CritSect () { DeleteCriticalSection (&_critSection); }
 
 private:
  void Acquire ()
  {
   EnterCriticalSection (&_critSection);
  }
  void Release ()
  {
   LeaveCriticalSection (&_critSection);
  }
 
 private:
  CRITICAL_SECTION _critSection;
};

这里聪明的部分是我们确保每一个进入临界区的客户最后都可以离开。"进入"临界区的状态是一种资源,并应当被封装。封装器通常被称作一个锁(lock)。

class Lock
{
 public:
  Lock (CritSect& critSect) : _critSect (critSect)
  {
   _critSect.Acquire ();
  }
  ~Lock ()
  {
   _critSect.Release ();
  }

 private
  CritSect & _critSect;
};

锁一般的用法如下:

void Shared::Act () throw (char *)
{
 Lock lock (_critSect);
 // perform action —— may throw
 // automatic destructor of lock
}

注意无论发生什么,临界区都会借助于语言的机制保证释放。
  还有一件需要记住的事情——每一种资源都需要被分别封装。这是因为资源分配是一个非常容易出错的操作,是要资源是有限提供的。我们会假设一个失败的资源分配会导致一个异常——事实上,这会经常的发生。所以如果你想试图用一个石头打两只鸟的话,或者在一个构造函数中申请两种形式的资源,你可能就会陷入麻烦。只要想想在一种资源分配成功但另一种失败抛出异常时会发生什么。因为构造函数还没有全部完成,析构函数不可能被调用,第一种资源就会发生泄露。
这种情况可以非常简单的避免。无论何时你有一个需要两种以上资源的类时,写两个小的封装器将它们嵌入你的类中。每一个嵌入的构造都可以保证删除,即使包装类没有构造完成。

智能指针

我们至今还没有讨论最常见类型的资源——用操作符new分配然后用指针访问的一个对象。我们需要为每个对象分别定义一个封装类吗?(事实上,C++标准模板库已经有了一个模板类,叫做auto_ptr,其作用就是提供这种封装。我们一会儿再解释auto_ptr。)让我们从一个极其简单、呆板但安全的东西开始。看下面的Smart Pointer模板类,它十分坚固,甚至无法实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值