您好,登錄后才能下訂單哦!
維基百科:
寫入時復制(英語:Copy-on-write,簡稱COW)是一種計算機程序設計領域的優化策略。其核心思想是,如果有多個調用者(callers)同時要求相同資源(如內存或磁盤上的數據存儲),他們會共同獲取相同的指針指向相同的資源,直到某個調用者試圖修改資源的內容時,系統才會真正復制一份專用副本(private copy)給該調用者,而其他調用者所見到的最初的資源仍然保持不變。這過程對其他的調用者都是透明的(transparently)。此作法主要的優點是如果調用者沒有修改該資源,就不會有副本(private copy)被創建,因此多個調用者只是讀取操作時可以共享同一份資源。
String類中的寫時拷貝技術是指用淺拷貝的方法拷貝其他對象,多個指針指向同一塊空間,只有當對其中一個對象修改時,才會開辟一個新的空間給這個對象,和它原來指向同一空間的對象不會受到影響。寫時拷貝的效率遠高于深拷貝。
可以通過增加一個成員變量count來實現寫時拷貝,這個變量叫做引用計數,統計這塊空間被多少個對象的_str同時指向。當用指向這塊空間的對象拷貝一個新的對象出來時count+1,當指向這塊空間的一個對象指向別的空間或析構時count-1。只有當count等于0時才可以釋放這塊空間,否則說明還有其他對象指向這塊空間,不能釋放。
count應該是什么類型呢?如果是int類型。
class String { public: String(const char* str) :_str(new char[strlen(str)+1]) ,_count(1) { strcpy(_str, str); } String(String& s) :_str(s._str) { ++s._count; _count = s._count; } ~String() { if (--_count == 0) { delete[] _str; } } private: char* _str; int _count; }; void Test() { String s1("aaaaaaaaa"); String s2(s1); }
雖然s1._count和s2._count都等于2,但是當s2執行析構函數后
現在只剩下s1一個對象指向這塊空間,s1._count和s2._count應該都變為1,但是s1._count沒有改變,查看s1._count和s2._count的地址發現它們并不是同一個地址,改變count只對當前對象有效,其他對象不會受到影響,無法實現引用計數。
這說明count是公共的,可以被多個對象同時訪問的。如果是static int類型
class String { public: String(const char* str) :_str(new char[strlen(str)+1]) { _count = 1; strcpy(_str, str); } String(String& s) :_str(s._str) { ++_count; } ~String() { if (--_count == 0) { delete[] _str; } } private: char* _str; static int _count; }; int String::_count = 0; void Test() { String s1("aaaaaaaaa"); String s2(s1); String s3(s2); String s4("bbbbbbbbb"); String s5(s4); }
現在s1、s2、s3的引用計數應該是3,s4、s5的引用計數應該是2。
但是結果不正確。原因是s1、s2、s3指向同一塊空間后count增加到3,構造s4時又把count設置為1,s4拷貝構造s5后count增加到2。說明這5個對象共用一個count,不能實現引用計數。
如果一個對象第一次開辟空間存放字符串時再開辟一塊新的空間存放引用計數,當它拷貝構造其他對象時讓其他對象的引用計數都指向存放引用計數的同一塊空間,count設置為int*類型,就可以實現引用計數了。
class String { public: String(const char* str) :_str(new char[strlen(str)+1]) ,_pCount(new int(1)) { strcpy(_str, str); } String(String& s) :_str(s._str) ,_pCount(s._pCount) { ++(*_pCount); } String& operator=(const String& s) { if (/*this != &s ||*/ _str != s._str) //防止自己給自己賦值,或自己拷貝的對象給自己賦值 { //釋放原對象 if (--(*_pCount) == 1) { delete _pCount; delete[] _str; } //淺拷貝增加引用計數 _str = s._str; _pCount = s._pCount; ++(*_pCount); } return *this; } ~String() { if (--*_pCount == 0) { delete _pCount; delete[] _str; } } protected: char* _str; int* _pCount; };
但是這種方法也存在不足:
1、它每次new兩塊空間,創建多個對象時效率較低于下面這種方法;
2、它多次分配小塊空間,容易造成內存碎片化,導致分配不出來大塊內存。
還有一種方法是在開辟_str時多開辟4個字節,在這塊空間的頭部保存引用計數。
class String { public: String(const char* str) :_str(new char[strlen(str)+5]) { _str += 4; strcpy(_str, str); //(*(int*)(_str-4)) = 1; _GetRefCount(_str) = 1; } String(const String& s) :_str(s._str) { //*((int*)(_str-4)) += 1; _GetRefCount(_str)++; } String& operator=(const String& s) { if (/*this != &s ||*/ _str != s._str) //防止自己給自己賦值,或自己拷貝的對象給自己賦值 { /*if (--(*(int*)(_str-4)) == 0) { delete[] (_str-4); }*/ _Release(); _str = s._str; ++(*(int*)(s._str-4)); } return *this; } ~String() { /*if (--(*(int*)(_str-4)) == 0) { delete[] (_str-4); }*/ _Release(); } //operator[]的特殊性,讀時也拷貝 char& operator[](size_t index) { //當引用計數大于1,需要寫時拷貝 if (_GetRefCount(_str) > 1) { char* tmp = new char[strlen(_str) + 5]; --_GetRefCount(_str); //new空間后再減引用計數,防止new空間失敗 tmp += 4; _GetRefCount(tmp) = 1; _str = tmp; } return _str[index]; } protected: int& _GetRefCount(char* _ptr) { return *((int*)(_ptr-4)); } //--引用計數,如果引用計數等于0,釋放 void _Release() { if (/*--(*(int*)(_str-4))*/--_GetRefCount(_str) == 0) { delete[] (_str-4); } } protected: char* _str; };
void COWTest() { String s1("aaaaaaaaaaa"); String s2(s1); String s3(s1); //operator[]的特殊性,讀時也拷貝 cout<<s1[0]<<endl; //寫時拷貝 s1[0] = '1'; }
當對s1修改后,s1指向新拷貝出來的空間。
推薦文章: 《C++ STL string的Copy-On-Write技術》
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。