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类型指针是分开的,而如果将这两个成员封装在一起的话呢?

      未完待续……

      

  • 相关阅读:
    tile38 复制配置
    The Guardian’s Migration from MongoDB to PostgreSQL on Amazon RDS
    tile38 一款开源的geo 数据库
    sqler sql 转rest api 的docker 镜像构建(续)使用源码编译
    sqler sql 转rest api javascript 试用
    sqler sql 转rest api redis 接口使用
    sqler sql 转rest api 的docker image
    sqler sql 转rest api 的工具试用
    apache geode 试用
    benthos v1 的一些新功能
  • 原文地址:https://www.cnblogs.com/Tour/p/4042106.html
Copyright © 2011-2022 走看看