zoukankan      html  css  js  c++  java
  • 智能指针与句柄类(三)

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

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

     1 class CUseCount 
     2 { 
     3 public: 
     4     CUseCount(); 
     5     CUseCount(const CUseCount&);
     6     CUseCount& operator=(const CUseCount&);
     7     virtual ~CUseCount() = 0; 
     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 }
    View Code
    • 构造函数中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 }
    View Code

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

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

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

     1class 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 }
    View Code

    代码中可以看出,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 }
      View Code

      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++》中的引用计数章节,加上了一些自己的个人理解,文章中不免有误,还请大家指正。

      未完待续……

  • 相关阅读:
    10055
    国外程序员推荐:每个程序员都应该读的非编程书
    Volume 0. Getting Started
    如何成为一名 Google 软件工程师?【Google招聘信息】作者: 丁鑫哲
    毕设-家校通
    如何快速创建数据库连接字符串
    LeetCode day13
    LeetCode day12
    LeetCode day11
    LeetCode day10 Called it
  • 原文地址:https://www.cnblogs.com/Tour/p/4043887.html
Copyright © 2011-2022 走看看