首页 > 代码库 > 智能指针与句柄类(三)
智能指针与句柄类(三)
之前文章中实现的写时复制,句柄类中引用计数和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() const41 {42 return shareable; 43 }44 45 bool CUseCount::isShared() const46 { 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 = http://www.mamicode.com/""); 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 = http://www.mamicode.com/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 = http://www.mamicode.com/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()),如下代码:View Code
1 char& String::operator[](int index) 2 { 3 if (value->isShareable()) 4 { 5 value = http://www.mamicode.com/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 = http://www.mamicode.com/new StringValue(value->data),重新构造value是没有必要的。而判断如果是if(isShared()),此时refCount==1,所以没有共享if返回false,不会重新构造value。
这一节中的代码,借鉴了《more effective C++》中的引用计数章节,加上了一些自己的个人理解,文章中不免有误,还请大家指正。
未完待续……
智能指针与句柄类(三)