引用计数的写时拷贝

什么是写时拷贝

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

#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;
        }
    }
};

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

原文地址:https://www.cnblogs.com/chan0311/p/9427335.html