写时拷贝(Copy On Write)方案详解
本文旨在通過對 寫時拷貝 的四個方案(Copy On Write)分析,讓大家明白寫時拷貝的實現(xiàn)及原理。
關(guān)于淺拷貝與深拷貝,我在之前的博客中已經(jīng)闡述過了?
淺拷貝容易出現(xiàn)指針懸掛的問題,深拷貝效率低,但是我們可以應(yīng)用引用計數(shù)來解決淺拷貝中多次析構(gòu)的問題,寫時拷貝也就應(yīng)運而生了。
首先要清楚寫時拷貝是利用淺拷貝來解決問題!!
方案一
class String { private:char* _str;int _refCount; };方案一最不靠譜,它將用作計數(shù)的整形變量_refCount定義為類的私有成員變量,任何一個對象都有它自己的成員變量_refCount,它們互不影響,難以維護(hù)。只要拷貝出了對象,_refCount大于了0,每個對象在調(diào)用自己的析構(gòu)函數(shù)時--_refCount不等于0,那么它們指向的那塊內(nèi)存都將得不到釋放,無法達(dá)到我們要的效果。
//以下是對方案一的簡單實現(xiàn),大家可以結(jié)合上圖感受到方案一的缺陷class String { public:String(char* str = "") //不能strlen(NULL):_refCount(0){_str = new char[strlen( str) + 1];strcpy(_str, str);_refCount++;}String(String &s):_refCount( s._refCount) {_str = s._str;_refCount++;s._refCount = _refCount;//這里雖然可以讓兩個對象的_refCount相等,//但如果超過兩個對象的_str指針都指向同一塊內(nèi)存時,//就無法讓所有對象的_refCount都保持一致//這是方案一的缺陷之一}~String(){if (--_refCount == 0){delete[] _str;_str = NULL;cout << "~String " << endl;}}friend ostream& operator<<( ostream& output, const String &s); private:char* _str;int _refCount; }; ostream& operator<<( ostream& output, const String & s) {output << s._str;return output; } void Test() {String s1("aaa");String s2(s1);String s3(s2);cout << s1 << endl;cout << s2 << endl;cout << s3 << endl; }方案二
class String { private:char* _str;static int count; };設(shè)置了一個靜態(tài)整形變量來計算指向一塊內(nèi)存的指針的數(shù)量,每析構(gòu)一次減1,直到它等于0(也就是沒有指針在指向它的時候)再去釋放那塊內(nèi)存,看似可行,其實不然!
這個方案只適用于只調(diào)用一次構(gòu)造函數(shù)、只有一塊內(nèi)存的情形,如果多次調(diào)用構(gòu)造函數(shù)構(gòu)造對象,新構(gòu)造的對象照樣會改變count的值,那么以前的內(nèi)存無法釋放會造成內(nèi)存泄漏。
?
結(jié)合上圖和下面的代碼,我們可以清楚地看到該方案相比方案一的改善,以及缺陷
class String { public:String(char* str = "") //不能strlen(NULL){_str = new char[strlen( str) + 1];strcpy(_str, str);count++;}String(const String &s){_str = s._str;count++;}String& operator=( String& s) {_str = s._str;count++;return *this;}~String(){if (--count == 0){delete[] _str;_str = NULL;cout << "~String " << endl;}}friend ostream& operator<<( ostream& output, const String &s);friend istream& operator>>( istream& input, const String &s); private:char* _str;static int count; };int String::count = 0; //初始化count void Test() //用例測試 {String s1("abcdefg");String s2(s1);String s3;s3 = s2;cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;String s4("opqrst");String s5(s4);String s6 (s5);s6 = s4;cout << s4 << endl;cout << s5 << endl;cout << s6 << endl; }方案三
問題的關(guān)鍵是,我們不是要為每一個對象建立一個引用計數(shù),而是要為每一塊內(nèi)存設(shè)置一個引用計數(shù),只有這樣才方便我們?nèi)ゾS護(hù)。當(dāng)指向這塊內(nèi)存的指針數(shù)為0時,再去釋放它!
class String {private:char* _str;int* _refCount; };方案三設(shè)置了一個int型的指針變量用來引用計數(shù),每份內(nèi)存空間對應(yīng)一個引用計數(shù),而不是每個對象對應(yīng)一個引用計數(shù),而且內(nèi)存之間的引用計數(shù)互不影響,不會出現(xiàn)方案一和方案二出現(xiàn)的問題。
1.在實現(xiàn)賦值運算符重載時要謹(jǐn)慎,不要遇到下圖的情形
?s1指向內(nèi)存1,s2指向內(nèi)存2,利用s2拷貝出的對象s3也指向內(nèi)存塊2,這時候內(nèi)存塊1的引用計數(shù)等于1 ,內(nèi)存塊2的引用計數(shù)等于2。一切似乎都很正常,但是調(diào)用賦值運算符重載執(zhí)行語句:s2=s1后,錯誤慢慢顯現(xiàn)出來了。將s2指向內(nèi)存1 并把內(nèi)存1 的引用計數(shù)加1,這理所當(dāng)然,但是不能把s2原本指向的空間直接delete,s3還指向內(nèi)存2著呢!這里千萬在釋放一塊空間前,對指向這塊內(nèi)存的引用計數(shù)進(jìn)行檢查,當(dāng)引用計數(shù)為0的時候再去釋放,否則只做減引用計數(shù)就行。
//錯誤代碼String& operator=(String& s) {if (_str!= s._str){delete[] _str;delete _refCount; _str = s._str;_refCount = s._refCount;(*_refCount)++;} return *this;}
2.改變字符串的某個字符時要謹(jǐn)慎,不要遇到類似下圖所遇到的問題。
如果多個對象都指向同一塊內(nèi)存,那么只要一個對象改變了這塊內(nèi)存的內(nèi)容,那所有的對象都被改變了!!
如下圖:當(dāng)s1和s2都指向內(nèi)存塊1,s3經(jīng)過賦值運算符重載后也指向內(nèi)存塊1,現(xiàn)在s2如果對字符串進(jìn)行修改后,所有指向內(nèi)存塊1 的指針指向的內(nèi)容都會被改變!
可以用下圖的形式改善這種問題:新設(shè)置一塊內(nèi)存來存要改變的對象,這樣就不會影響其他的對象了
案例3我畫的圖較多,方便大家結(jié)合代碼去理解?
//案例三class String { public:String(char* str = "") //不能strlen(NULL){_refCount = new int(1); //給_refCount開辟空間,并賦初值1_size = strlen(str);_capacity = _size + 1;_str = new char[strlen(str) + 1];strcpy(_str, str);}String(const String &s){_refCount = s._refCount;_str = s._str;_size = strlen(s._str);_capacity = _size + 1;(*_refCount)++; //拷貝一次_refCount都要加1}//要考慮是s1=s2時,s1原先不為空的情況,要先釋放原內(nèi)存//如果要釋放原內(nèi)存時,要考慮它的_refCount減1后是否為0,為零再釋放,否則其它對象指針無法再訪問這片空間String& operator=(String& s) {if (_str!= s._str){_size = strlen(s._str);_capacity = _size + 1;if (--(*_refCount) == 0){delete[] _str;delete _refCount;}_str = s._str;_refCount = s._refCount;(*_refCount)++;} return *this;}
//如果修改了字符串的內(nèi)容,那所有指向這塊內(nèi)存的對象指針的內(nèi)容間接被改變//如果還有其它指針指向這塊內(nèi)存,我們可以從堆上重新開辟一塊內(nèi)存空間,//把原字符串拷貝過來//再去改變它的內(nèi)容,就不會產(chǎn)生鏈?zhǔn)椒磻?yīng)// 1.減引用計數(shù) 2.拷貝 3.創(chuàng)建新的引用計數(shù)char& String::operator[](const size_t index) //參考深拷貝 {if (*_refCount==1){return *(_str + index);}else{--(*_refCount);char* tmp = new char[strlen(_str)+1];strcpy(tmp, _str);_str = tmp;_refCount = new int(1);return *(_str+index);}}~String(){if (--(*_refCount)== 0) //當(dāng)_refCount=0的時候就釋放內(nèi)存{delete[] _str;delete _refCount;_str = NULL;cout << "~String " << endl;}_size = 0;_capacity = 0;}friend ostream& operator<<(ostream& output, const String &s);friend istream& operator>>(istream& input, const String &s); private:char* _str; //指向字符串的指針size_t _size; //字符串大小size_t _capacity; //容量int* _refCount; //計數(shù)指針 };ostream& operator<<(ostream& output, const String &s) {output << s._str;return output; } istream& operator>>(istream& input, const String &s) {input >> s._str;return input; }void Test() //用例測試 {String s1("abcdefg");String s2(s1);String s3;s3 = s2;cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;s2[3] = '0';cout << s1 << endl;cout << s2 << endl;cout << s3 << endl; }
方案四
class String {private:char* _str; };?
方案四與方案三類似。方案四把用來計數(shù)的整型指針變量放在所開辟的內(nèi)存空間的首部。
用*((int*)_str)就能取得計數(shù)值
class String { public:String(char * str = "" ) //不能strlen(NULL){_str = new char[strlen( str) + 5];_str += 4;strcpy(_str, str);GetRefCount(_str) = 1;}String(const String &s){_str = s._str;++GetRefCount(_str);}//要考慮是s1=s2時,s1原先不為空的情況,要先釋放原內(nèi)存//如果要釋放原內(nèi)存時,要考慮它的_refCount減1后是否為0,//為零再釋放,否則其它對象指針無法再訪問這片空間String& operator=(String& s){if (this != &s ){if (GetRefCount(_str ) == 1){delete (_str-4);_str = s._str;++GetRefCount(_str );}else{--GetRefCount(_str );_str = s._str;++GetRefCount(_str );}}return *this ;}//如果修改了字符串的內(nèi)容,那所有指向這塊內(nèi)存的對象指針的內(nèi)容間接被改變//如果還有其它指針指向這塊內(nèi)存,我們可以從堆上重新開辟一塊內(nèi)存空間,//把原字符串拷貝過來.//再去改變它的內(nèi)容,就不會產(chǎn)生鏈?zhǔn)椒磻?yīng)char& String ::operator[](const size_t index ) //深拷貝 {if (GetRefCount(_str) == 1){return _str[index ];}else{// 1.減引用計數(shù)--GetRefCount(_str );// 2.拷貝 3.創(chuàng)建新的引用計數(shù)char* tmp = new char [strlen(_str) + 5]; *((int *)tmp) = 1;tmp += 4;strcpy(tmp, _str);_str = tmp;return _str[index ];}}int& GetRefCount(char* ptr) //獲取引用計數(shù)(隱式內(nèi)聯(lián)函數(shù)){return *((int *)(ptr -4));}~String(){if (--GetRefCount(_str) == 0){cout << "~String" << endl;delete[] (_str-4); }}friend ostream& operator<<( ostream& output, const String &s);friend istream& operator>>( istream& input, const String &s); private:char* _str;};ostream& operator<<(ostream& output, const String &s) {output << s._str;return output; } istream& operator>>(istream& input, const String &s) {input >> s._str;return input; }void Test() //用例測試 {String s1("abcdefg" );String s2(s1);String s3;s3 = s2;cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;s2[3] = '0';cout << s1 << endl;cout << s2 << endl;cout << s3 << endl; }?轉(zhuǎn)載于:https://www.cnblogs.com/Lynn-Zhang/p/5400714.html
總結(jié)
以上是生活随笔為你收集整理的写时拷贝(Copy On Write)方案详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python numpy中数组.min(
- 下一篇: Git学习笔记(0)-错误汇总