zoukankan      html  css  js  c++  java
  • 引用计数的写时拷贝

    什么是写时拷贝

    首先我们需要知道什么是写时拷贝,写时拷贝,通俗点说也就是写的时候拷贝。那么什么是写的时候拷贝呢,这又是什么意思呢?
    举个例子,创建一个日期类的对象,然后又用这个对象拷贝构造了多个对象,也就是说这几个对象所指向的是同一块空间,那么当你对其中一个对象进行读操作的时候,什么问题都不会有,那么当你对某个对象进行写操作的时候,问题就出现了,一个对象的改变会影响其他对象,但是这并不是我们想要的,虽然说共用同一块空间,但是对象是独立的。我的改变应该不会影响别人。那么为了解决这个问题,我们就引入了写时拷贝这个概念,就是说,当你要对某个对象进行写才做,而这个对象又与其他对象共用同一块空间,此时,就需要,再重新开一段空间,把你要进行写操作的那个对象拷贝过来,然后再进行写操作,这样就不会影响其他的对象了。
    下面一段代码来解释一下

    #include<iostream>
    using namespace std;
    
     class String 
      { 
      private: 
        char* _str; 
      public:
        String(char* str = "")
            :_str(new char [strlen(str)+1])
        {
            strcpy(_str,str);
            cout<<"String(char*)"<<endl;
    
        }
        String(const String&str)
            :_str(str._str)
        {
            cout<<"String(const &)"<<endl;
    
        }
    
        ~String()
        {
            if(_str!=NULL)
            delete []_str;
            _str = NULL;
            cout<<"~String"<<endl;
    
        }
      }; 
    int main()
    {
        String s1("qwer");
        String s2(s1);
        String s3(s2);
    
        return 0;
    }

    运行结果如下图
    这里写图片描述
    图中我们可以看到,三个对象的地址是相同的,也就是是,指向同一块空间,但是按照常规的特点,三个对象应该是要析构三次,但是在这里,因为是只有一块空间,析构一次后,再次析构系统就崩溃了。为了解决这个问题我们引入了引用计数,就是可以定义一个变量用来保存一块空间对象的个数,当要进行写操作的时候就按照上面所说的方法,先拷贝再写,但是需要改变引用计数。这样就可以实现写时拷贝了。

    写时拷贝的代码(引用计数)

    class String
    {
    private:
        char *_str;
        int *_refCountPtr;
        int _capacity;
        int _size;
    public:
        String(char *str = "")
            :_refCountPtr(new int(1))
        {
            _capacity = strlen(str);
            _size = _capacity;
            _str = new char[_capacity+1];
            strcpy(_str,str);
    
            cout<<"String(char *str = "")"<<endl;
        }
    
        String(String&s)
            :_str(s._str)
            ,_capacity(0)
            ,_size(0)
        {
            _refCountPtr = s._refCountPtr;//改变引用计数
            (*_refCountPtr)++;
    
            cout<<"String(String&s)"<<endl;
    
        }
        String &operator = (String&s)
        {
            cout<<"operator="<<endl;
            if(_str!=s._str)//自己给自己赋值
            {
                if((*_refCountPtr)==1)
                {
                    delete[]_str;
                    delete _refCountPtr;
                }
                _str = s._str;
                _refCountPtr = s._refCountPtr;
                (*_refCountPtr)++;
            }
            return *this;
        }
    
        ~String()
        {
            Release();
            cout<<"~String()"<<endl;
    
        }
    };

    还有另外一种方法来实现写时拷贝,具体思想就是,在构造对象的时候就为它多开辟四个字节用来存引用计数,这样就不需要变量了,要用引用计数的的时候只需要把它取出来就可以了。

    写时拷贝(指针)

    class String
    {
    private:
        char* _pStr;
    public:
        String(const char* pStr = "")
        {
            if(NULL == pStr)//传了一个空字符串
            {
                _pStr = new char[1 + 4];//用了一个指针,多四个字节
                _pStr = (char*)(((int*)_pStr)+1);//向后走四个字节
                *_pStr = '';
            }
            else
            {
                _pStr = new char[strlen(pStr) + 1 + 4];
                _pStr = (char*)(((int*)_pStr)+1);
                strcpy(_pStr, pStr);
            }
    
            *((int*)_pStr - 1) = 1;//引用计数初始化为1
        }
        String(const String&s)//直接拷贝,让引用计数加加就好,后面的事析构会做
            :_pStr(s._pStr)
        {
            ++GetCount();
        }
        String& operator=(const String& s)
        {
            if(this != &s)//判断是否自己给自己赋值
            {
                Release();//因为这里要析构,一般我们不会显示的调用析构,所以封装一个函数来代替
                _pStr = s._pStr;
                ++GetCount();
            }
    
            return *this;
        }
    
        ~String()
        {
            Release();
        }
    
    private:
        int& GetCount()const
        {
            return *((int*)_pStr - 1);//把引用计数取出来
        }
    
        void Release()
        {
            if(_pStr && (0 ==GetCount()--))
            {
                _pStr = (char*)((int*)_pStr-1);
                delete[] _pStr;
                _pStr = NULL;
            }
        }
    };

    上面这两种方法都可以实现写时拷贝。

  • 相关阅读:
    WinForm常用代码
    XML编程与应用-读取XML
    基础SQL语句
    WPF基础——继承
    wpf控件
    手机网页支付
    Application_Start 多次启动问题
    更改Outlook 2013中Exchange数据文件存放路径
    MySql双机热备份
    图片轮播(Jquery)
  • 原文地址:https://www.cnblogs.com/chan0311/p/9427335.html
Copyright © 2011-2022 走看看