首页 > 代码库 > 智能指针与句柄类(二)

智能指针与句柄类(二)

  之前文章提到写时复制(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()const34 { 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<typename T>   15 inline CHandler<T>::CHandler(T* p):ptr(p) //u 默认构造函数   16 {}   17 18 template<typename T>   19 inline CHandler<T>::CHandler(const CHandler& rhs):u(rhs.u), ptr(rhs.ptr)//u 拷贝构造函数会将引用计数+1   20 {}   21  22 template<typename T>   23 inline CHandler<T>& CHandler<T>::operator=(const CHandler& rhs)   24 {25     if(u.reattach())//是否仍有句柄绑定到此对象26         delete ptr;27     ptr = rhs.ptr;28     return *this;29 }30 31 template<typename T>32 inline CHandler<T>::~CHandler()//同构造函数 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_error10         ("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_error22         ("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类型指针是分开的,而如果将这两个成员封装在一起的话呢?

  未完待续……

  

智能指针与句柄类(二)