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

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

    修改之前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<class T>   
    15 inline Handle<T>::Handle(T* p):ptr(p) //u 默认构造函数   
    16 {}   
    17 
    18 template<class T>   
    19 inline Handle<T>::Handle(const Handle& rhs):u(rhs.u), ptr(rhs.ptr)//u 拷贝构造函数会将引用计数+1   
    20 {}
    21  
    22 template<class T>
    23 inline Handle<T>& Handle<T>::operator=(const Handle& rhs)   
    24 {
    25     if(u.reattach())//是否仍有句柄绑定到此对象
    26         delete ptr;
    27     ptr = rhs.ptr;
    28     return *this;
    29 }
    30 
    31 template<class T>
    32 inline Handle<T>::~Handle()//同构造函数 u 的析构函数被调用
    33 {
    34     if(u.only())    //引用计数只有唯一一个对象时,则进行delete操作
    35         delete ptr;
    36 }
    View Code

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

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

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

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

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

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

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

      未完待续……

      

  • 相关阅读:
    hdu 1251(字典树)
    关于sass和less做自适应网页的区别
    重置css样式
    打印网页局部内容的方法
    关于在IE下面promise兼容的解决办法
    关于vue属性绑定的问题
    vue的过渡动画在除了chrome浏览器外的浏览器下不正常的问题
    jq 的replaceWith方法在360下面会出现兼容问题
    element-ui 的 upload组件的clearFiles方法调用方法
    canvas 转化为 img
  • 原文地址:https://www.cnblogs.com/Tour/p/4042106.html
Copyright © 2011-2022 走看看