http://blog.51cto.com/zgw285763054/1839752
維基百科:
寫入時複製(英語:Copy-on-write,簡稱COW)是一種計算機程序設計領域的優化策略。其核心思想是,如果有多個調用者(callers)同時要求相同資源(如內存或磁盤上的數據存儲),他們會共同獲取相同的指針指向相同的資源,直到某個調用者試圖修改資源的內容時,系統纔會真正複製一份專用副本(private copy)給該調用者,而其他調用者所見到的最初的資源仍然保持不變。這過程對其他的調用者都是透明的(transparently)。此作法主要的優點是如果調用者沒有修改該資源,就不會有副本(private copy)被創建,因此多個調用者只是讀取操作時可以共享同一份資源。
String類中的寫時拷貝技術是指用淺拷貝的方法拷貝其他對象,多個指針指向同一塊空間,只有當對其中一個對象修改時,纔會開闢一個新的空間給這個對象,和它原來指向同一空間的對象不會受到影響。寫時拷貝的效率遠高於深拷貝。
可以通過增加一個成員變量count來實現寫時拷貝,這個變量叫做引用計數,統計這塊空間被多少個對象的_str同時指向。當用指向這塊空間的對象拷貝一個新的對象出來時count+1,當指向這塊空間的一個對象指向別的空間或析構時count-1。只有當count等於0時纔可以釋放這塊空間,否則說明還有其他對象指向這塊空間,不能釋放。
count應該是什麼類型呢?如果是int類型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
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執行析構函數後
![20160818004856.png wKiom1e0lYnj-bOGAABTs5LqMs8002.png-wh_50](http://static.javashuo.com/static/loading.gif)
現在只剩下s1一個對象指向這塊空間,s1._count和s2._count應該都變爲1,但是s1._count沒有改變,查看s1._count和s2._count的地址發現它們並不是同一個地址,改變count只對當前對象有效,其他對象不會受到影響,無法實現引用計數。
這說明count是公共的,可以被多個對象同時訪問的。如果是static int類型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
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。
![20160818010245.png wKioL1e0mMPhC5sgAACS0fxKwZk012.png-wh_50](http://static.javashuo.com/static/loading.gif)
但是結果不正確。原因是s1、s2、s3指向同一塊空間後count增加到3,構造s4時又把count設置爲1,s4拷貝構造s5後count增加到2。說明這5個對象共用一個count,不能實現引用計數。
如果一個對象第一次開闢空間存放字符串時再開闢一塊新的空間存放引用計數,當它拷貝構造其他對象時讓其他對象的引用計數都指向存放引用計數的同一塊空間,count設置爲int*類型,就可以實現引用計數了。
![20160818104358.png wKiom1e1IQWjg4hxAAA36elJxOg735.png-wh_50](http://static.javashuo.com/static/loading.gif)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
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個字節,在這塊空間的頭部保存引用計數。
![20160818104938.png wKioL1e1IlSyOZsyAAAo9y7E_Zg815.png-wh_50](http://static.javashuo.com/static/loading.gif)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
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
protected
:
int
& _GetRefCount(
char
* _ptr)
{
return
*((
return
*((
int
*)(_ptr-4));
}
//--引用計數,如果引用計數等於0,釋放
void
_Release()
}
|