深拷貝效率低,我們可以應引用計數的方式去解決淺拷貝中析構多次的問題。
首先要清楚寫時拷貝是利用淺拷貝來解決問題!!
方案一
class String { private: char* _str; int _refCount; };
方案一最不靠譜,它將用作計數的整形變量_refCount定義為類的私有成員變量,任何一個對象都有它自己的成員變量_refCount,它們互不影響,只要拷貝出了對象,_refCount大於了1,那麼每個對象調用自己的析構函數時--_refCount不等於0,那麼它們指向的那塊內存都將得不到釋放,無法達到我們要的效果。
1 //以下是對方案一的簡單實現,大家可以結合上圖感受到方案一的缺陷 2 3 #define _CRT_SECURE_NO_WARNINGS 1 4 5 #include<iostream> 6 using namespace std; 7 #include<assert.h> 8 9 class String 10 { 11 public: 12 String(char* str = "") //不能strlen(NULL) 13 :_refCount(0) 14 { 15 _str = new char[strlen( str) + 1]; 16 strcpy(_str, str); 17 _refCount++; 18 } 19 String(String &s) 20 :_refCount( s._refCount) 21 { 22 _str = s._str; 23 _refCount++; 24 s._refCount = _refCount; 25 26 //這裡雖然可以讓兩個對象的_refCount相等, 27 //但如果超過兩個對象的_str指針都指向同一塊內存時, 28 //就無法讓所有對象的_refCount都保持一致 29 //這是方案一的缺陷之一 30 } 31 ~String() 32 { 33 if (--_refCount == 0) 34 { 35 delete[] _str; 36 _str = NULL; 37 cout << "~String " << endl; 38 } 39 } 40 friend ostream& operator<<( ostream& output, const String &s); 41 private: 42 char* _str; 43 int _refCount; 44 }; 45 ostream& operator<<( ostream& output, const String & s) 46 { 47 output << s._str; 48 return output; 49 } 50 void Test() 51 { 52 String s1("aaa"); 53 String s2(s1); 54 String s3(s2); 55 cout << s1 << endl; 56 cout << s2 << endl; 57 cout << s3 << endl; 58 } 59 int main() 60 { 61 Test(); 62 system("pause"); 63 return 0; 64 } 方案一
方案二
class String { private: char* _str; static int count; };
設置一個靜態整形變量來計算指向一塊內存的指針的數量,每析構一次減1,直到它等於0(也就是沒有指針在指向它的時候)再去釋放那塊內存,看似可行,其實不然!
這個方案只適用於只調用一次構造函數、只有一塊內存的情形,如果多次調用構造函數構造對象,新構造的對象照樣會改變count的值,那麼以前的內存無法釋放會造成內存洩漏。
結合上圖和下面的代碼,我們可以清楚地看到該方案相比方案一的改善,以及缺陷
1 #define_CRT_SECURE_NO_WARNINGS 1 2 3 4 #include<iostream> 5 using namespace std; 6 #include<assert.h> 7 8 class String 9 { 10 public: 11 String(char* str = "") //不能strlen(NULL) 12 { 13 _str = new char[strlen( str) + 1]; 14 strcpy(_str, str); 15 16 count++; 17 } 18 String(const String &s) 19 { 20 _str = s._str; 21 count++; 22 23 } 24 String& operator=( String& s) 25 { 26 _str = s._str; 27 count++; 28 return *this; 29 } 30 ~String() 31 { 32 if (--count == 0) 33 { 34 delete[] _str; 35 _str = NULL; 36 cout << "~String " << endl; 37 } 38 } 39 friend ostream& operator<<( ostream& output, const String &s); 40 friend istream& operator>>( istream& input, const String &s); 41 private: 42 char* _str; 43 static int count; 44 }; 45 ostream& operator<<( ostream& output, const String & s) 46 { 47 output << s._str; 48 return output; 49 } 50 istream& operator>>( istream& input, const String & s) 51 { 52 input >> s._str; 53 return input; 54 } 55 56 int String::count = 0; //初始化count 57 58 void Test() 59 { 60 String s1("aaa"); 61 String s2(s1); 62 String s3 = s2; 63 cout << s1 << endl; 64 cout << s2 << endl; 65 cout << s3 << endl; 66 67 } 68 int main() 69 { 70 Test(); 71 system("pause"); 72 return 0; 73 } 方案二
方案三
class String { private: char* _str; int* _refCount; };
方案三設置了一個int型的指針變量用來引用計數,每份內存空間對應一個引用計數,而不是每個對象對應一個引用計數,而且每塊內存的引用計數互不影響,不會出現方案一和方案二出現的問題。
1.在實現賦值運算符重載要謹慎,不要遇到下圖的情形
2.改變字符串的某個字符時要謹慎,不要遇到類似下圖所遇到的問題。
如果多個對象都指向同一塊內存,那麼只要一個對象改變了這塊內存的內容,那所有的對象都被改變了!!
可以用下圖的形式改善這種問題:新設置一塊內存來存要改變的對象
案例3我畫的圖較多,方便大家結合代碼去理解
1 #define _CRT_SECURE_NO_WARNINGS 1 2 3 #include<iostream> 4 using namespace std; 5 #include<assert.h> 6 7 class String 8 { 9 public: 10 String(char* str = "") //不能strlen(NULL) 11 { 12 _refCount = new int(1); //給_refCount開辟空間,並賦初值1 13 _size = strlen(str); 14 _capacity = _size + 1; 15 _str = new char[strlen(str) + 1]; 16 strcpy(_str, str); 17 } 18 String(const String &s) 19 { 20 _refCount = s._refCount; 21 _str = s._str; 22 _size = strlen(s._str); 23 _capacity = _size + 1; 24 (*_refCount)++; //拷貝一次_refCount都要加1 25 26 } 27 28 //要考慮是s1=s2時,s1原先不為空的情況,要先釋放原內存 29 //如果要釋放原內存時,要考慮它的_refCount減1後是否為0,為零再釋放,否則其它對象指針無法再訪問這片空間 30 String& operator=(String& s) 31 { 32 if (_str!= s._str) 33 { 34 _size = strlen(s._str); 35 _capacity = _size + 1; 36 if (--(*_refCount) == 0) 37 { 38 delete[] _str; 39 delete _refCount; 40 } 41 42 _str = s._str; 43 _refCount = s._refCount; 44 (*_refCount)++; 45 } 46 return *this; 47 } 48 //如果修改了字符串的內容,那所有指向這塊內存的對象指針的內容間接被改變 49 //如果還有其它指針指向這塊內存,我們可以從堆上重新開辟一塊內存空間, 50 //把原字符串拷貝過來 51 //再去改變它的內容,就不會產生鏈式反應 52 // 1.減引用計數 2.拷貝 3.創建新的引用計數 53 char& String::operator[](const size_t index) //參考深拷貝 54 { 55 if (*_refCount==1) 56 { 57 return *(_str + index); 58 } 59 else 60 { 61 --(*_refCount); 62 char* tmp = new char[strlen(_str)+1]; 63 strcpy(tmp, _str); 64 _str = tmp; 65 _refCount = new int(1); 66 return *(_str+index); 67 } 68 } 69 ~String() 70 { 71 if (--(*_refCount)== 0) //當_refCount=0的時候就釋放內存 72 { 73 delete[] _str; 74 delete _refCount; 75 _str = NULL; 76 cout << "~String " << endl; 77 } 78 _size = 0; 79 _capacity = 0; 80 } 81 friend ostream& operator<<(ostream& output, const String &s); 82 friend istream& operator>>(istream& input, const String &s); 83 private: 84 char* _str; //指向字符串的指針 85 size_t _size; //字符串大小 86 size_t _capacity; //容量 87 int* _refCount; //計數指針 88 }; 89 90 91 ostream& operator<<(ostream& output, const String &s) 92 { 93 output << s._str; 94 return output; 95 } 96 istream& operator>>(istream& input, const String &s) 97 { 98 input >> s._str; 99 return input; 100 } 101 102 void Test() //用例測試 103 { 104 String s1("abcdefg"); 105 String s2(s1); 106 String s3; 107 s3 = s2; 108 cout << s1 << endl; 109 cout << s2 << endl; 110 cout << s3 << endl; 111 s2[3] = '0'; 112 cout << s1 << endl; 113 cout << s2 << endl; 114 cout << s3 << endl; 115 116 //String s4("opqrst"); 117 //String s5(s4); 118 //String s6 (s5); 119 //s6 = s4; 120 //cout << s4 << endl; 121 //cout << s5 << endl; 122 //cout << s6 << endl; 123 124 } 125 int main() 126 { 127 Test(); 128 system("pause"); 129 return 0; 130 } 方案三
方案四
class String { private: char* _str; };
方案四與方案三類似。方案四把用來計數的整形變量放在所開辟的內存空間的首部,
用*((int*)_str)就能取得計數值
1 #define_CRT_SECURE_NO_WARNINGS 1 2 3 #include<iostream> 4 using namespace std; 5 #include<assert.h> 6 7 class String 8 { 9 public: 10 String(char * str = "" ) //不能strlen(NULL) 11 { 12 _str = new char[strlen( str) + 5]; 13 _str += 4; 14 strcpy(_str, str); 15 GetRefCount(_str) = 1; 16 } 17 String(const String &s) 18 { 19 _str = s._str; 20 ++GetRefCount(_str); 21 } 22 23 //要考慮是s1=s2時,s1原先不為空的情況,要先釋放原內存 24 //如果要釋放原內存時,要考慮它的_refCount減1後是否為0, 25 //為零再釋放,否則其它對象指針無法再訪問這片空間 26 String& operator=(String& s) 27 { 28 if (this != &s ) 29 { 30 if (GetRefCount(_str ) == 1) 31 { 32 delete (_str-4); 33 _str = s._str; 34 ++GetRefCount(_str ); 35 } 36 else 37 { 38 --GetRefCount(_str ); 39 _str = s._str; 40 ++GetRefCount(_str ); 41 } 42 } 43 return *this ; 44 } 45 //如果修改了字符串的內容,那所有指向這塊內存的對象指針的內容間接被改變 46 //如果還有其它指針指向這塊內存,我們可以從堆上重新開辟一塊內存空間, 47 //把原字符串拷貝過來. 48 //再去改變它的內容,就不會產生鏈式反應 49 50 51 char& String ::operator[](const size_t index ) //深拷貝 52 { 53 54 if (GetRefCount(_str) == 1) 55 { 56 return _str[index ]; 57 } 58 else 59 { 60 // 1.減引用計數 61 --GetRefCount(_str ); 62 // 2.拷貝 3.創建新的引用計數 63 char* tmp = new char [strlen(_str) + 5]; 64 *((int *)tmp) = 1; 65 tmp += 4; 66 strcpy(tmp, _str); 67 _str = tmp; 68 return _str[index ]; 69 } 70 } 71 72 int& GetRefCount(char* ptr) //獲取引用計數(隱式內聯函數) 73 { 74 return *((int *)(ptr -4)); 75 } 76 ~String() 77 { 78 if (--GetRefCount(_str) == 0) 79 { 80 cout << "~String" << endl; 81 delete[] (_str-4); 82 } 83 84 } 85 friend ostream& operator<<( ostream& output, const String &s); 86 friend istream& operator>>( istream& input, const String &s); 87 private: 88 char* _str; 89 90 }; 91 92 93 ostream& operator<<(ostream& output, const String &s) 94 { 95 output << s._str; 96 return output; 97 } 98 istream& operator>>(istream& input, const String &s) 99 { 100 input >> s._str; 101 return input; 102 } 103 104 void Test() //用例測試 105 { 106 String s1("abcdefg" ); 107 String s2(s1); 108 String s3; 109 s3 = s2; 110 cout << s1 << endl; 111 cout << s2 << endl; 112 cout << s3 << endl; 113 s2[3] = '0'; 114 cout << s1 << endl; 115 cout << s2 << endl; 116 cout << s3 << endl; 117 118 //String s4("opqrst"); 119 //String s5(s4); 120 //String s6 (s5); 121 //s6 = s4; 122 //cout << s4 << endl; 123 //cout << s5 << endl; 124 //cout << s6 << endl; 125 126 } 127 int main() 128 { 129 Test(); 130 system("pause" ); 131 return 0; 132 } 方案四