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

      未完待续……

  • 相关阅读:
    BZOJ 3744 Gty的妹子序列
    BZOJ 3872 Ant colony
    BZOJ 1087 互不侵犯
    BZOJ 1070 修车
    BZOJ 2654 tree
    BZOJ 3243 向量内积
    1003 NOIP 模拟赛Day2 城市建设
    CF865D Buy Low Sell High
    CF444A DZY Loves Physics
    Luogu 4310 绝世好题
  • 原文地址:https://www.cnblogs.com/Tour/p/4043887.html
Copyright © 2011-2022 走看看