分析 寫時拷貝 的四個方案(Copy On Write),copywrite
深拷貝效率低,我們可以應引用計數的方式去解決淺拷貝中析構多次的問題。
首先要清楚寫時拷貝是利用淺拷貝來解決問題!!
方案一
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 }
方案四