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