Skip to content

Latest commit

 

History

History
82 lines (62 loc) · 2.63 KB

File metadata and controls

82 lines (62 loc) · 2.63 KB

条款14:在资源管理类中小心copying行为

有时你需要建立自己的资源管理类。

例如,假设我们使用C API函数处理类型为Mutex的互斥体对象,有lock和unlock两个函数可用:

void lock(Mutex* pm);
void unlock(Mutex* pm);

为确保不会忘记将一个被锁住的Mutex解锁,你可能希望建议一个class来管理锁。这样的class基本结构由RAII守则支配:

class Lock {
public:
  explicit Lock(Mutex* pm)
    : mutexPtr(pm)
  { lock(mutexPtr); }
  ~Lock() { unlock(mutexPtr); }
private:
  Mutex* mutexPtr;
};

客户对Lock的用法符合RAII方式:

Mutex m;
...
{
  Lock ml(&m);  // 锁定互斥体
  ...
}               // 互斥体解锁

如果Lock对象被复制,会发生什么事?

Lock ml1(&m);   // 锁定m
Lock ml2(ml1);  // 将ml1复制到ml2

一般化的问题是,当一个RAII对象被复制,会发生什么事?大多数情况你会选择以下两种可能:

  • 禁止复制。 许多时候允许RAII对象被复制并不合理,你应该禁止。将copying操作声明为private。对Lock而言看起来是这样的:
class Lock : private Uncopyable {
public:
  ...
};
  • 对底层资源采用引用计数。 有时我们希望保有资源,直到它的最后一个使用者被销毁。这种情况下复制RAII对象时,应该将资源的引用数递增。

通常只要内含 std::shared_ptr 成员变量,RAII便可实现引用计数。如果前述Lock打算使用引用计数,它可以改变mutexPtr的类型为 std::shared_ptr<Mutex>。但是当我们使用一个Mutex,我们想要的释放操作是解除锁定而非删除。

我们可以通过指定 std::shared_ptr 的删除器实现,当引用计数为0时调用。删除器是其构造函数的第二个缺省参数,代码如下:

class Lock {
public:
  explicit Lock(Mutex* pm)
    : mutexPtr(pm, unlock) {
    lock(mutexPtr.get());
  }
private:
  std::shared_ptr<Mutex> mutexPtr;
};

本例中Lock类不再声明析构函数,因为mutexPtr的析构函数会在互斥体引用计数为0时自动调用删除器解锁互斥体。

  • 复制底层资源。 可以使用深拷贝复制资源管理对象。
  • 转移底层资源的所有权。 你可能希望确保永远只有一个RAII对象指向一个未加工资源,即使被复制也是如此。此时资源所有权会被转移给目标对象。

请记住

  • 复制RAII对象必须复制它锁管理的资源,所以资源的copying行为决定RAII对象的copying行为。
  • 普通而常见的RAII class copying行为是:抑制copying、使用引用计数。