智能指针与句柄类(三)

  之前文章中实现的写时复制,句柄类中引用计数和T类型指针是分开的,这里换一种方式来处理,将引用计数和T类型指针视为一个整体,当做句柄类模板参数。先对上节中的引用计数进行改造:

 1 class CUseCount
 2 {
 3 public:
 4     CUseCount();
 5     CUseCount(const CUseCount&);
 6     CUseCount& operator=(const CUseCount&);
 7     ~CUseCount();
 8
 9     void markUnshareable();
10     bool isShareable() const;
11     bool isShared() const;
12
13     void addReference();
14     void removeReference();
15
16 private:
17     int refCount;    //注意这里非int指针
18     bool shareable;    //是否是共享状态
19 };
20
21 CUseCount::CUseCount():refCount(0), shareable(true)
22 {}
23
24 CUseCount::CUseCount(const CUseCount& u):refCount(0), shareable(true)
25 {}
26
27 CUseCount& CUseCount::operator=(const CUseCount& u)
28 {
29     return *this;
30 }
31
32 CUseCount::~CUseCount()
33 {}
34
35 void CUseCount::markUnshareable()
36 {
37     shareable = false;
38 }
39
40 bool CUseCount::isShareable() const
41 {
42     return shareable;
43 }
44
45 bool CUseCount::isShared() const
46 {
47     return refCount > 1;
48 }
49
50 void CUseCount::addReference()
51 {
52     ++refCount;
53 }
54
55 void CUseCount::removeReference()
56 {
57     if(--refCount == 0)
58         delete this;
59 }

这个版本的UseCount和之前的版本差别很大,从析构函数可以看出(纯虚函数),它是基于引用计数来共享的值对象的基类,需要注意的部分:

  • 构造函数中refCount设置为0而不是1,说明此时还没有某个对象在引用它。对refCount的增加由构造它的对象调用addReference来实现。shareable用了表示此对象是否可以被共享。
  • 拷贝构造函数并没有根据拷贝的对象进行一些初始化动作,而是与构造函数完成相同的功能。想想在什么情况下会调用UseCount的拷贝构造函数,是在句柄类不在共享状态时,进行写时复制,需要调用拷贝构造函数,而这里UseCount是不共享的,所以同样设置refCount=0,shareable=true。
  • 赋值操作符没有做任何事情,返回自身,没有将其设为private状态是因为UseCount的派生类可能调用operator=,从而调用到基类的operator=。
  • UseCount析构函数没有做任何事情,此版本中UseCount和T类型指视为一个整体一起当做句柄类的模板参数并且创建在堆上,这里句柄类调用引用计数的增加(addReference)与减少(removeReference),并不直接调用delete,删除引用计数,UseCount的销毁是通过removeReference中refCount的值来判断的,由于使用了delete this,所以必须保证此对象创建在堆上。

  接下来看看这一版本中的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 private:
10     T* ptr;
11     void init();
12 };
13
14 template<class T>
15 inline Handle<T>::init()
16 {
17     if(ptr == 0)
18         return;
19     if(ptr->isShareable() == false)    //非共享状态,进行复制
20         ptr = new T(*ptr);
21
22     ptr->addReference();    //引用计数增加
23 }
24
25 template<class T>
26 inline Handle<T>::Handle(T* p):ptr(p) //u 默认构造函数
27 {
28     init();
29 }
30
31 template<class T>
32 inline Handle<T>::Handle(const Handle& rhs):ptr(rhs.ptr)
33 {
34     init();
35 }
36
37 template<class T>
38 inline Handle<T>& Handle<T>::operator=(const Handle& rhs)
39 {
40     if(ptr != rhs.ptr)
41     {
42         if(ptr != NULL)
43         {
44             ptr->removeReference();
45         }
46         ptr = rhs.ptr;
47         init();
48     }
49     return *this;
50 }
51
52 template<class T>
53 inline Handle<T>::~Handle()
54 {
55     if(ptr)
56         ptr->removeReference();
57 }

修改后的Handle类比之前的要复杂一些:

  • 观察ptr调用的函数,不难看出T类型继承自UseCount。
  • init()函数提供统一接口,对T类型进行写时复制,并完成对引用技术值的增加。
  • 析构函数并不delete ptr,而是减少引用计数值,让其自己判断是否析构。

  使用本节中实现的UseCount和Handle来处理写时复制,一个String类的例子如下:

 1 ?class String
 2 {
 3 public:
 4     String(const char *value = "");
 5     const char& operator[](int index) const;
 6     char& operator[](int index);
 7 private:
 8     struct StringValue: public CUseCount    //继承自引用计数
 9     {
10         char *data;
11         StringValue(const char *initValue);
12         StringValue(const StringValue& rhs);
13         void init(const char *initValue);
14         ~StringValue();
15     };
16     Handle<StringValue> value;
17 };
18
19 void String::StringValue::init(const char *initValue)
20 {
21     data = new char[strlen(initValue) + 1];
22     strcpy(data, initValue);
23 }
24
25 String::StringValue::StringValue(const char *initValue)
26 {
27     init(initValue);
28 }
29
30 String::StringValue::StringValue(const StringValue& rhs)
31 {
32     init(rhs.data);
33 }
34
35 String::StringValue::~StringValue()
36 {
37     delete [] data;
38 }
39
40 String::String(const char *initValue): value(new StringValue(initValue))
41 {}
42
43 const char& String::operator[](int index) const    //const函数不进行复制
44 {
45     return value->data[index];
46 }
47
48 char& String::operator[](int index)
49 {
50     if (value->isShared())
51     {
52         value = new StringValue(value->data);    //先调用基类UseCount拷贝构造函数
53     }
54     value->markUnshareable();
55     return value->data[index];
56 }

代码中可以看出,String内部实现由StringValue完成,Handle句柄托管StringValue类型指针,StringValue继承自UseCount类,其子对象部分负责char*的管理,,父对象即UseCount完成对象的计数、共享以及销毁工作,以图来表示例子中各个类的关系如下:

代码中还有一些需要的地方:

  • StringValue是一个中间层,实现char*的管理和引用计数功能,而对用户来说它是不可见的,Handle对象实现对StringValue的托管。
  • String类在实现进行写时复制的函数时,需要操作引用计数对象,由于Handle对StringValue进行了托管,所有操作都在Handle对象上进行。
  • const版本的operator[]没有用到写时复制,在非const版本中进行写时复制。
  • 进行复制的条件if(value->isShared()) ,而不是if(value->isShareable()),注意这两者的区别,isShared()表示当前句柄托管的对象是否与其他句柄共享,而isShareable()表示是否可以进行共享。举个例子,如果将判断条件改为if(value->isShareable()),如下代码:

     1 char& String::operator[](int index)
     2 {
     3     if (value->isShareable())
     4     {
     5         value = new StringValue(value->data);    //先调用基类UseCount拷贝构造函数
     6     }
     7     value->markUnshareable();
     8     return value->data[index];
     9 }
    10
    11 int main()
    12 {
    13     String s("Grubby");
    14     char c = s[3];
    15
    16     return 0;
    17 }

    main函数中line14,调用了operator[] 操作符,按上述代码实现,line3先判断if(value->isShareable()),而s对象刚刚定义没有使用,UseCount构造函数中设置isShare=true,所以if判断返回true,运行语句value = new StringValue(value->data),重新构造value是没有必要的。而判断如果是if(isShared()),此时refCount==1,所以没有共享if返回false,不会重新构造value。

  这一节中的代码,借鉴了《more effective C++》中的引用计数章节,加上了一些自己的个人理解,文章中不免有误,还请大家指正。

  未完待续……

时间: 2024-08-09 23:50:54

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

智能指针与句柄类(一)

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

智能指针与句柄类(四)

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

智能指针与句柄类(二)

之前文章提到写时复制(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, 句柄类无法访问priva

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++ 句柄类

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

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

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

C++中智能指针的设计和使用

转载请标明出处,原文地址:http://blog.csdn.net/hackbuteer1/article/details/7561235      智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露.它的一种通用实现技术是 使用引用计数(reference count).智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针.每次创建类的新对象时,初始化指针并将引用计数置为