条款14:在资源管理类中小心拷贝行为
此条款,针对对象管理资源的补充,内容分为两个部分:
- 为什么小心拷贝行为
- 怎么解决
一、为什么小心拷贝行为
解释为什么,原书直接给了一个有关死锁的例子:
void lock(Mutex* pm); //锁定pm所指的互斥器
void unlock(Mutex* pm); //将互斥器解锁
class Lock
{
public:
//获得资源
explicit Lock(Mutex* pm) :mutexPtr(pm) {
lock(mutexPtr);
}
//释放资源
~Lock() { unlock(mutexPtr); }
private:
Mutex *mutexPtr;
};
Mutex m;
void f()
{
Lock m1(&m);//管理Mutex对象
//...
Lock m2(m1); //拷贝
}//函数结束后,自动调用ml的析构函数解锁
因为m2拷贝时会调用lock函数对Mutex对象进行加锁,但是m对象此时处于使用状态,因为其会阻塞等待,等待m1释放互斥量,但是m1此时也无法向下执行,因此这个程序就产生死锁了
所以对于Lock这个类,是不希望有拷贝行为的。
二、怎么解决
1、禁止复制
(1)将 copying 操作声明为 private
(2)或者设计一个不可以被复制的类
class Toby: private UncopyToby{
public:
...
}
2、对底层资源使用 “引用计数法”
我们可以参考shared_ptr类那样的机制,对资源设计“引用计数法”。
此机制为:当引用计数为0时,我们希望做的是解锁资源而不是删除资源,但是我们的shared_ptr提供一个“删除器”,删除器可以是一个函数或函数对象,因此下面我们为mutexPtr提供了unlokc函数,当引用计数为0时就会调用unlock函数,将Mutex对象解锁。
class Lock
{
public:
explicit Lock(Mutex* pm)
:mutexPtr(pm,unlock) //提供unlock函数为删除器
{
lock(mutexPtr.get()); //条款15会 谈到 get 函数
}
private:
std::shared_ptr<Mutex> mutexPtr; //采用智能指针代替普通指针
};
四、总结
复制 RAII 对象必须一并复制它所管理的资源,所以资源的 copying 行为决定 RAII 对象的 copying 行为
普通而常见的 RAII class copying 行为是:抑制 copying、实行引用计数法。不过其他行为也都可能被实现。