博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++ String类写时拷贝 4
阅读量:4096 次
发布时间:2019-05-25

本文共 4360 字,大约阅读时间需要 14 分钟。

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执行析构函数后

wKiom1e0lYnj-bOGAABTs5LqMs8002.png-wh_50

    现在只剩下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。

wKioL1e0mMPhC5sgAACS0fxKwZk012.png-wh_50


    但是结果不正确。原因是s1、s2、s3指向同一块空间后count增加到3,构造s4时又把count设置为1,s4拷贝构造s5后count增加到2。说明这5个对象共用一个count,不能实现引用计数。

    如果一个对象第一次开辟空间存放字符串时再开辟一块新的空间存放引用计数,当它拷贝构造其他对象时让其他对象的引用计数都指向存放引用计数的同一块空间,count设置为int*类型,就可以实现引用计数了。

wKiom1e1IQWjg4hxAAA36elJxOg735.png-wh_50

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个字节,在这块空间的头部保存引用计数。

    wKioL1e1IlSyOZsyAAAo9y7E_Zg815.png-wh_50


    

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 
*((
int
*)(_ptr-4));
    
}
 
    
//--引用计数,如果引用计数等于0,释放
    
void 
_Release()
    
{
        
if 
(
/*--(*(int*)(_str-4))*/
--_GetRefCount(_str) == 0)
        
{
            
delete
[] (_str-4);
        
}
    
}
 
protected
:
    
char
* _str;
};

    

1
2
3
4
5
6
7
8
9
10
11
12
void 
COWTest()
    
{
        
String s1(
"aaaaaaaaaaa"
);
        
String s2(s1);
        
String s3(s1);
 
        
//operator[]的特殊性,读时也拷贝
        
cout<<s1[0]<<endl;
         
        
//写时拷贝
        
s1[0] = 
'1'
;
    
}

    当对s1修改后,s1指向新拷贝出来的空间。

wKiom1e1MhPB-qKzAAAvTg48J2Q244.png-wh_50


wKioL1e1Mh7jx0EUAABJ_CjTlQ4677.png-wh_50


wKioL1e1NjrRyOI-AAA-HOpOlFw930.png-wh_50


wKioL1e1Npzx5JikAAA9nwxQX3c473.png-wh_50


    

    推荐文章:

转载地址:http://oeoii.baihongyu.com/

你可能感兴趣的文章
【Unity】封装SQLite管理类
查看>>
【Unity】面试题整理
查看>>
【C#】如何实现一个迭代器
查看>>
【Unity】Destroy和DestroyImmediate的区别
查看>>
【Lua】Mac系统下配置SublimeText的Lua编译环境
查看>>
【C#】利用Conditional属性完成编译忽略
查看>>
【Unity】微信登录后将头像存为bytes,将bytes读取成sprite图片
查看>>
【Unity】使用GPS定位经纬度
查看>>
【UGUI/NGUI】一键换Text/Label字体
查看>>
【C#】身份证本地验证
查看>>
【Unity】坑爹的Bug
查看>>
【算法】求数组中某两个数的和为目标值
查看>>
如何高效学习动态规划?
查看>>
动态规划法(六)鸡蛋掉落问题(一)
查看>>
LeetCode 887.鸡蛋掉落(C++)
查看>>
奇异值分解(SVD)的原理详解及推导
查看>>
算法数据结构 思维导图学习系列(1)- 数据结构 8种数据结构 数组(Array)链表(Linked List)队列(Queue)栈(Stack)树(Tree)散列表(Hash)堆(Heap)图
查看>>
求LCA最近公共祖先的离线Tarjan算法_C++
查看>>
Leetcode 834. 树中距离之和 C++
查看>>
【机器学习】机器学习系统SysML 阅读表
查看>>