最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!
条款13中描述了使用智能指针来管理基于堆分配的资源,然而并非所有资源都是基于堆分配的,对那种资源而言,像 shared_ptr
这样的智能指针往往不适合作为资源管理者,所以需要建立自己的资源管理类。
假如我们正在使用互斥锁 Mutex
void lock(Mutex* pm); //锁住pm指向的锁
void unlock(Mutex* pm); //解锁pm指向的锁
为了不会忘记将一个被锁住的 Mutex
解锁,我们可能希望建立一个类来管理互斥锁,这样的类的基本结构要遵守 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); //构造锁ml,锁住m
... //执行临界区操作
} //临界区结束,调用ml的析构函数,解锁
到目前为止都是很顺利的,但如果 Lock
对象被拷贝,会发生什么呢?
Lock ml1(&m); //锁定m
Lock ml2(ml1); //把ml1拷贝进ml2,会发生什么?
在面对这种情况时,我们需要对拷贝行为制定一些规则,我们由以下选择:
-
禁止复制。大多数时候允许 RAII 对象被复制并不合理,如上面的拷贝行为相当于给同一个资源上两个锁,所以我们需要禁止复制,具体操作可看条款06。
-
对底层资源使用“引用计数法”
有时我们希望持有资源,直到它的最后一个使用者被销毁,而复制 RAII 对象时,引用计数+1,引用计数为0时释放。
shared_ptr
便是这样。只要资源管理类中有一个
shared_ptr
成员变量,就可以实现引用计数。我们可以把Lock
中的mutexPtr
成员变量的类型改为shared_ptr<Mutex>
。然而shared_ptr
中引用计数为0时执行的操作是删除存储的指针,shared_ptr
允许构造时指定所谓的“删除器”,当计数为0时调用该“删除器”。class Lock{ public: explicit Lock(Mutex* pm):mutexPtr(pm, unlock) //将unlock函数绑定到删除器 { lock(mutexPtr.get()); //智能指针可以通过get()获得存储的指针 } //这里其实不需要定义析构函数,因为计数为0时会调用智能指针的析构函数 private: shared_ptr<Mutex> mutexPtr; //使用shared_ptr,不使用裸指针 };
-
复制底部资源
有时候我们可以拥有某个资源的多份拷贝,那么我们的资源管理类就要确保每一份拷贝都要在使用周期结束后释放资源,并且每一份拷贝互不干涉,因此拷贝这样的对象就要拷贝它包含的所有资源,进行深拷贝。例如当对象包含一个指针,我们必须先生成一个指针的拷贝,分配一个新的内存空间再把数据拷贝过来,这就是深拷贝。如果是浅拷贝,拷贝则直接使用了本体的指针成员,没有生成指针的拷贝,那么两个对象的指针成员就会指向同一个地址,删除拷贝就会导致本体被删除。
Note:
- 复制 RAII 对象必须一并复制它所管理的资源,所以资源的拷贝行为决定 RAII 对象的拷贝行为
- 常见的 RAII 对象拷贝行为的应对方法是:禁止拷贝、使用引用计数法