假如我们使用C API函数处理类型为Mutex的互斥对象,共有lock和unlock两函数可用:
- void lock(Mutex* pm);//锁定pm所指的互斥器
- void unlock(Mutex* pm);//将互斥器接触锁定
为确保绝对不忘记将被锁定的Mutex解锁,你可能会建立一个class用来管理锁。这样的类基本结构由RAII守则支配,也就是“资源在构造期间获得,析构期间释放”:
- class Lock
- {
- public:
- explicit Lock(Mutex* pm):mutexPtr(pm)
- {
- lock(mutexPtr);//获得资源
- }
- ~Lock()
- {
- unlock(mutexPtr);//释放资源
- }
- private:
- Mutex *mutexPtr;
- };
客户对Lock的用法符合RAII方式:
- class Lock
- Mutex m;//定义你需要的互斥器
- ...
- { //建立一个区块来定义critical section
- Lock(&m);//锁定互斥器
- ...//执行critical section内的操作
- }//在区块最末尾,自动解除互斥锁
这很好,但是如果Lock对象被复制,会发生什么?
- Lock m1(&m);//锁定
- Lock m2(m1);//复制
当一个RAII对象被复制,会发生什么,大多数时候有以下几种选择:
1.禁止复制
许多时候允许RAII对象被复制并不合理。对一个像Lock这样的类这是可能的,因为很少能够合理拥有“同步化基础物”的副本。如果复制对RAII并不合理,你应该禁止复制。条款6告诉我们怎么做:将copying操作声明为private,对Lock而言看起来是这样的:
- class Lock : private Uncopyable//禁止复制
- {
- public:
- ..
- };
2.对底层资源祭出“引用计数法”
有时候我们希望保持资源,直到它最后一个使用者被销毁,这种情况下复制RAII对象时,应该将资源的“被引用数”递增,trl::shared_ptr便是如此。
通常只要内含一个tr1::shared_ptr成员变量,RAII类便可实现出引用计数行为。如果前述Lock打算使用使用技术,它可以改变mutexPtr类型,将它从Mutex*改为tr1::shared_ptr<Mutex>。然而不幸tr1::shared_ptr的缺省行为是“当引用次数为0时删除其所指物”,那不是我们所要的行为。幸运的是tr1::shared_ptr允许指定所谓的“删除器”,那是一个函数或函数对象,当引用次数为0时便调用。删除器对tr1::shared_ptr构造函数而言是可有可无的第二参数,所以代码看起来像这样:
- class Lock
- {
- public:
- explicit Lock(Mutex *pm)//以某个Mutex初始化shard_ptr并以unlock函数
- :mutexPtr(pm, unlock) //为删除器
- {
- lock(mutexPtr.get());//条款15谈到 get
- }
- private:
- std::tr1::shared_ptr<Mutex> mutexPtr; //使用shared_ptr
- };
本例的Lock不再声明析构函数,因为没有必要。条款5说过,类的析构函数会自动调用其non_static成员变量的析构函数。而mutexPtr的析构函数会在互斥器的引用次数为0的时候自动调用tr1::shared_ptr的删除器。
3.复制底层资源
有时候只要你喜欢,可以针对一份资源拥有其任意数量的副件,而你需要“资源管理类”的唯一理由是,当你不再需要某个副本的时候确保它被释放,在此情况下复制资源管理对象,应该同时也复制其所包覆的资源,也就是说,复制资源管理了对象时,进行的是“深度拷贝”。
4.转移底部资源的拥有权
某些罕见场合下你可能希望确保永远只有一个RAII对象指向一个未加工资源,即使RAII对象被复制依然如此。此时资源的拥有权会从被复制物转移到目标物。如条款13所诉。这是atuo_ptr奉行的复制意义。
copying函数有可能被编译器自动创建出来,因而除非编译器所生成版本做了你想要做的事情,否则你的自己编写它们。某些情况下你或许也想支持这些函数版本,这样的版本描述于条款45
复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。
普通而常见的RAII类copying行为是:抑制copying,施行引用计数法,不过其他行为都可能被实现。