智能指针与句柄类(二)

  之前文章提到写时复制(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 }

修改之前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 }

  使用引用计数封装后的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 }

以这种方式实现的写时复制,基本上所有操作都会调用原生指针的值拷贝(拷贝构造函数),因为除非对象之定义不使用,否则都会调用到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 }

代码中对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 }

要在具体(写)操作时实现写时复制,即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 }

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 };

 定义继承自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 }

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

  未完待续……

  

时间: 2024-08-11 11:43:29

智能指针与句柄类(二)的相关文章

智能指针与句柄类(一)

句柄类/智能指针(smart point)是存储指向动态分配(堆)对象指针的类.除了能够在适当的时间自动删除指向的对象外,他们的工作机制很像C++的内置指针.句柄类实际上是通过复制指向引用计数器类型的指针,来代替复制实际对象:从而使得复制对象变成复制指针,实现虚拟复制(即是用复制对象的地址代替复制对象本身),以提高内存效率和访问速度. 引用计数是复制控制成员中使用的技术,跟踪该类有多少个对象共享一个指针.引用计数与共享对象一起存储,需要创建一个单独类指向共享对象并管理引用计数.由构造函数而不是赋

智能指针与句柄类(四)

当我们希望使用容器来保存继承体系中的对象时,容器用于继承体系中的类型会有影响:派生类对象复制到基类对象时,派生类对象将被切掉.那么解决这一问题的方法通常是使用容器保存基类对象的指针,这些指针实际指向的是程序运行时动态分配的派生类对象,用户必须保证在容器析构前调用delete来释放动态分配的对象,如下例: 1 class Base 2 { 3 public: 4 virtual void action() = 0; 5 }; 6 class Derived1 : public Base 7 {..

智能指针与句柄类(三)

之前文章中实现的写时复制,句柄类中引用计数和T类型指针是分开的,这里换一种方式来处理,将引用计数和T类型指针视为一个整体,当做句柄类模板参数.先对上节中的引用计数进行改造: 1 class CUseCount 2 { 3 public: 4 CUseCount(); 5 CUseCount(const CUseCount&); 6 CUseCount& operator=(const CUseCount&); 7 ~CUseCount(); 8 9 void markUnshare

C++中智能指针的模板类

在C++中,智能指针是一个非常重要的概念.因为C++的类动态分配的对象不能自动释放掉,需手动调用new运算符.当程序员大意时,或程序发生异常时,或许就会发生没有手动释放内存而造成内存泄露. 智能指针的定义:就是在一个类中,存在一个指向另一个类对象的指针,并通过对指针运算符(比如:->,*)的重载,就可以实现利用当前类的对象通过指针运算符来操纵另一个类的成员(就像另一个类的指针操作一样),并且,在析构函数中定义了delete操作,借助于变量的作用域,能够实现类对象空间的自动释放. 在C++ 11中

C/C++——跟我重写智能指针auto_ptr模版类

第一次使用auto_ptr的时候感觉很好用,但是对内部原理根本不懂,心里不知道这东西到底是个什么东东,总是感觉这东东比较陌生.今天有时间来简单实现一下该类模版auto_ptr,实现了该模版类的主要功能,可以让大家了解一下这个东东内部到底是个什么情况. 栈对象和堆对象的区别: 首先,看一下两种类对象的区别,一个是在栈上分配空间,另一个是在堆上分配空间. 如果看到这里,你不清楚堆和栈的区别.那我也不解释了,自行Google..(如果你想baidu也不拦你) 1.先测试栈上分配的对象 #include

【C++】智能指针类和OpenCV的Ptr模板类

智能指针类 引用计数 智能指针(smart pointer)的一种通用实现技术是使用引用计数(reference count).智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象.引用计数为0时,删除对象. 其基本使用规则是: 每次创建类的新对象时,初始化指针并将引用计数置为1.当对象作为另一对象的副本而创建时,复制构造函数复制指针并增加与之相应的引用计数的值.对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数的值(如果引用计数减至0,则删除对

C++ 智能指针详解 二

智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露.它的一种通用实现技术是使用引用计数(reference count).智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针.每次创建类的新对象时,初始化指针并将引用计数置为1:当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数:对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果

C++ 句柄类

一.容器与继承 在容器中保存有继承关系的对象时,如果定义成保存基类对象,则派生类将被切割,如果定义成保存派生类对象,则保存基类对象又成问题(基类对象将被强制转换成派生类对象,而派生类中定义的成员未被初始化).     唯一的可行的选择是容器中保存对象的指针.但是需要用户管理对象和指针.C++中一个通用的技术是包装类(cover)或句柄类(handle).用句柄类存储和管理类指针. 句柄类大体上完成两方面的工作: 管理指针,这与智能指针的功能类似. 实现多态,利用动态绑定,是得指针既可以指向基类,

C++单线程智能指针实现

阅读目录 1.智能指针是什么 2.普通指针存在的问题 3.什么是引用计数 4.智能指针实现 4.1.基础对象类 4.2.辅助类 4.3.为基础对象类实现智能指针类 4.4.智能指针类的使用与测试 5.智能指针类的改进一 6.智能指针改进二 正文 文章也发布在 腾讯云+社区 一直以来都对智能指针一知半解,看C++Primer中也讲的不够清晰明白(大概是我功力不够吧).最近花了点时间认真看了智能指针,特地来写这篇文章. 回到顶部 1.智能指针是什么 简单来说,智能指针是一个类,它对普通指针进行封装,