智能指针与句柄类(二)

心已入冬 提交于 2020-03-30 16:44:12

  之前文章提到写时复制(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类型指针是分开的,而如果将这两个成员封装在一起的话呢?

  未完待续……

  

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!