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

  1. void lock(Mutex* pm);//锁定pm所指的互斥器 
  2. void unlock(Mutex* pm);//将互斥器接触锁定 

为确保绝对不忘记将被锁定的Mutex解锁,你可能会建立一个class用来管理锁。这样的类基本结构由RAII守则支配,也就是“资源在构造期间获得,析构期间释放”:

  1. class Lock 
  2. {  
  3. public:  
  4.   explicit Lock(Mutex* pm):mutexPtr(pm)  
  5.   {  
  6.     lock(mutexPtr);//获得资源  
  7.   }  
  8.   ~Lock()  
  9.   {  
  10.     unlock(mutexPtr);//释放资源  
  11.   }  
  12. private:  
  13.   Mutex *mutexPtr;  
  14. };  

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

  1. class Lock  
  2. Mutex m;//定义你需要的互斥器  
  3. ...  
  4. {  //建立一个区块来定义critical section  
  5. Lock(&m);//锁定互斥器  
  6. ...//执行critical section内的操作  
  7.   
  8. }//在区块最末尾,自动解除互斥锁  

这很好,但是如果Lock对象被复制,会发生什么?

  1. Lock m1(&m);//锁定 
  2. Lock m2(m1);//复制 

当一个RAII对象被复制,会发生什么,大多数时候有以下几种选择:

1.禁止复制

许多时候允许RAII对象被复制并不合理。对一个像Lock这样的类这是可能的,因为很少能够合理拥有“同步化基础物”的副本。如果复制对RAII并不合理,你应该禁止复制。条款6告诉我们怎么做:将copying操作声明为private,对Lock而言看起来是这样的:

  1. class Lock : private Uncopyable//禁止复制 
  2. public
  3.   .. 
  4. }; 

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构造函数而言是可有可无的第二参数,所以代码看起来像这样:

  1. class Lock 
  2. public
  3.   explicit Lock(Mutex *pm)//以某个Mutex初始化shard_ptr并以unlock函数 
  4.     :mutexPtr(pm, unlock) //为删除器 
  5.   { 
  6.     lock(mutexPtr.get());//条款15谈到 get 
  7.   } 
  8. private
  9.   std::tr1::shared_ptr<Mutex> mutexPtr; //使用shared_ptr 
  10. }; 

本例的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,施行引用计数法,不过其他行为都可能被实现。