智能指针与句柄类(二)

  之前文章提到写时复制(copy-on-write)技术,要实现这种功能,针对上文中Handle代码,需要将size_t * use这个抽象出来,封装成一个引用计数类,提供写时复制功能。CUseCount类实现如下:

 1 class CUseCount  2 {  3 public:  4  CUseCount();  5     CUseCount(const CUseCount&);  6     ~CUseCount();  7  
 8     bool only()const;   //判断引用计数是否为0, 句柄类无法访问private int*p, 故提供此函数 
 9     bool reattach(const CUseCount&);    //对计数器的操作, 用来代替 operator = 
10  
11     bool makeonly();    //写时复制, 表示是否需要赋值对象本身 
12  
13 private: 14     CUseCount& operator=(const CUseCount&); //提供reattach函数代替 operator = 
15     int *p; //实现计数 
16 }; 17 
18 CUseCount::CUseCount():p(new int(1)) 19 {} 20  
21 CUseCount::CUseCount(const CUseCount& u):p(u.p) 22 { 23     ++*p; 24 } 25  
26 CUseCount::~CUseCount() 27 { 28     if(--*p == 0) 29  delete p; 30     p = 0; 31 } 32  
33 bool CUseCount::only()const
34 { 35     return *p == 1; 36 } 37  
38 bool CUseCount::reattach(const CUseCount& u) 39 { 40     ++*u.p;     //避免 this == &u, 先对 *u.p 加一 
41     if(--*p == 0)   //如果引用计数值为0删除 this->p, 重新绑定p 
42  { 43  delete p; 44         p = u.p; 45         return true;//返回true表示句柄此时绑定对象引用数为0, 可删除 
46  } 47     p=u.p;      //如果引用计数值不为0,只是重新绑定p 
48     return false;   //返回false表示此时仍有句柄绑定到此对象,不可删除 
49 } 50  
51 bool CUseCount::makeonly() 52 { 53     if(*p == 1) //确保句柄唯一, 则不需要进行复制 
54         return false; 55     //其他情况则必须复制 
56     --*p; 57     p = new int(1); 58     return true; 59 }
View Code

修改之前Handle代码,在其中定义CUseCount对象:

 1 template<class T> class Handle  2 {  3 public:  4     Handle(T *p = 0);  5     Handle(const Handle& h);  6     Handle& operator=(const Handle&);  7     ~Handle();  8     //other member functions
 9 private: 10     T* ptr; 11     CUseCount u;    //将引用计数类抽象
12 }; 13 
14 template<class T>   
15 inline Handle<T>::Handle(T* p):ptr(p) //u 默认构造函数 
16 {} 17 
18 template<class T>   
19 inline Handle<T>::Handle(const Handle& rhs):u(rhs.u), ptr(rhs.ptr)//u 拷贝构造函数会将引用计数+1 
20 {} 21  
22 template<class T>
23 inline Handle<T>& Handle<T>::operator=(const Handle& rhs) 24 { 25     if(u.reattach())//是否仍有句柄绑定到此对象
26  delete ptr; 27     ptr = rhs.ptr; 28     return *this; 29 } 30 
31 template<class T>
32 inline Handle<T>::~Handle()//同构造函数 u 的析构函数被调用
33 { 34     if(u.only())    //引用计数只有唯一一个对象时,则进行delete操作
35  delete ptr; 36 }
View Code

   使用引用计数封装后的Handle类运行前一篇中的例子时,仍然会出现*hp随着*hp2重新赋值而改变的情况,此版本的句柄类(即Handle模板类)情况下要实现copy-on-write,目前我能想到的有两种方式:

1 重载句柄类的 operator-> 和 operator* :

 1 template<class T>
 2 inline T& Handle<T>::operator*()  3 {  4     if(u.makeonly())  5  delete ptr;  6     ptr = new T(*ptr);  7 
 8     if(ptr) return *ptr;  9     throw std::runtime_error 10         ("dereference of unbound Handle"); 11 } 12 
13 template<class T>
14 inline T* Handle<T>::operator->() 15 { 16     if(u.makeonly()) 17  delete ptr; 18     ptr = new T(*ptr); 19 
20     if(ptr) return ptr; 21     throw std::runtime_error 22         ("access through of unbound Handle"); 23 }
View Code

以这种方式实现的写时复制,基本上所有操作都会调用原生指针的值拷贝(拷贝构造函数),因为除非对象之定义不使用,否则都会调用到operator->和operator*两个函数,而有些操作并没有改变原生指针指向的内容,还是发生了拷贝,很简单的例子:

1 int main() 2 { 3     Handle<int> hp(new int(12)); 4     Handle<int> hp2(hp); 5     cout<<*hp<<"  "<<*hp2<<endl; 6 
7     return 0; 8 }
View Code

代码中对hp2的操作是读操作,但程序依然调用了operator*,进行写时复制,这种情况下是没有必要的,注意到重载的是非const版本的operator->和operator*,而对于const版本的两个函数,默认不进行写时复制,这样就得在编码过程中尽量使用const对象了。

2 写时复制对应具体的操作。不重载operator->和operator*函数,而是具体需要修改T类型成员变量时,进行写时复制,分析下例:

1 int main() 2 { 3     Handle<string> hp(new string("Grubby")); 4     Handle<string> hp2(hp); 5     char c = hp[3]; 6     c = 'e'; 7 
8     return 0; 9 }
View Code

要在具体(写)操作时实现写时复制,即hp调用operator[]时,那么Handle类必须重载operator[],在其中完成复制工作:

1 template<T>
2 char & Handle<T>::operator[](int index) 3 { 4     if(u.makeonly()) 5         ptr = new T(*ptr); 6     return *ptr[index]; 7 }
View Code

Handle作为模板类,其中托管的T类型未知,如果针对每个T类型都要试Handle重载一些具体的写操作,那么Handle类会爆炸掉,想到一个方法来避免此种情况,是Handle作为基类,而完成写时复制的类继承自Handle,比如上例中,可以如下修改:

首先修改Handle中的访问标签:

 1 template<class T> class Handle  2 {  3 public:  4     Handle(T *p = 0);  5     Handle(const Handle& h);  6     Handle& operator=(const Handle&);  7     ~Handle();  8     //other member functions
 9 protected:   //使派生类也可以访问此标签下的成员 
10     T* ptr; 11     CUseCount u;    //将引用计数类抽象
12 };
View Code

 定义继承自Handle<string>的派生类:

 1 class StrHandle : public Handle<string>
 2 {  3 public:  4     char & operator[](int index)    //实现写时复制
 5  {  6         if(u.makeonly())  7             ptr = new T(*ptr);  8         return *ptr[index];  9  } 10 }; 11 
12 int main() 13 { 14     StrHandle<string> hp(new string("Grubby")); 15     StrHandle<string> hp2(hp); 16     char c = hp[3]; 17     c = 'e'; 18 
19     return 0; 20 }
View Code

  文中提到的两种实现写时复制方法都比较复杂,不是很直观,添加了很多额外的代码,文中实现的句柄类中引用计数和T类型指针是分开的,而如果将这两个成员封装在一起的话呢?

  未完待续……

  

你可能感兴趣的:(指针)