引用計數的寫時拷貝

什麼是寫時拷貝

首先我們需要知道什麼是寫時拷貝,寫時拷貝,通俗點說也就是寫的時候拷貝。那麼什麼是寫的時候拷貝呢,這又是什麼意思呢?
舉個例子,創建一個日期類的對象,然後又用這個對象拷貝構造了多個對象,也就是說這幾個對象所指向的是同一塊空間,那麼當你對其中一個對象進行讀操作的時候,什麼問題都不會有,那麼當你對某個對象進行寫操作的時候,問題就出現了,一個對象的改變會影響其他對象,但是這並不是我們想要的,雖然說共用同一塊空間,但是對象是獨立的。我的改變應該不會影響別人。那麼爲了解決這個問題,我們就引入了寫時拷貝這個概念,就是說,當你要對某個對象進行寫才做,而這個對象又與其他對象共用同一塊空間,此時,就需要,再重新開一段空間,把你要進行寫操作的那個對象拷貝過來,然後再進行寫操作,這樣就不會影響其他的對象了。
下面一段代碼來解釋一下

#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 = '\0';
        }
        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;
        }
    }
};

上面這兩種方法都可以實現寫時拷貝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章