有时你需要建立自己的资源管理类。
例如,假设我们使用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、使用引用计数。