C++Day09 深拷贝、写时复制(cow)、短字符串优化
一、std::string 的底层实现
1、深拷贝
1 class String{
2 public:
3 String(const String &rhs):m_pstr(new char[strlen(rhs) + 1]()){
4 }
5 private:
6 char* m_pstr;
7 };
这种实现方式,在需要对字符串进行频繁复制而又并不改变字符串内容时,效率比较低下。如果对一块空间只是进行读,就没必要采用深拷贝,当需要进行写的时候,再使用深拷贝申请新的空间
2、写时复制 (浅拷贝+引用计数)
当只是进行读操作时,就进行浅拷贝,如果需要进行写操作的时候,再进行深拷贝;再加一个引用计数,多个指针指向同一块空间,记录同一块空间的对象个数
- std::string之写时复制
当两个std::string发生复制或者赋值时,不会复制字符串内容,而是增加一个引用计数,然后字符串指针进行浅拷贝,其执行效率为O(1)。只有当修改其中一个字符串内容时,才执行真正的复制。

- 引用计数存在哪里?
堆区,为了好获取将将引用计数与数据放在一起,并且最好在数据前面,这样当数据变化的时候不会移动引用计数的位置

1 class String{
2 public:
3 String():m_str(new char[5]() + 4){
4 //new 5 表示4个字节存放引用计数,一个字节存放\0 +4 是为了将指针指向字符串位置
5 cout << "String()" << endl;
6 //引用计数初始化为1 (字符指针向前偏移,指向引用计数,并且转为int型指针,解引用得引用计数的值)
7 InitRefCount();
8 }
9
10 String(const char* str):m_str(new char[strlen(str) + 5] + 4){ //有参构造
11 //申请空间大小,4B引用计数,还有\0
12 cout << "Sting(const char *)" << endl;
13 strcpy(m_str, str);
14 InitRefCount();
15 }
16
17 String(const String &rhs):m_str(rhs.m_str){ //浅拷贝
18 cout << "String(const String &rhs)" << endl;
19 IncreaseRefCount();
20 }
21
22 String &operator=(const String &rhs){ //赋值
23 if(this != &rhs){
24 DecreaseRefCount();
25 if(0 == getRefcount()){ //如果该空间引用计数为0,删掉该空间
26 delete [] (m_str - 4);
27 }
28 m_str = rhs.m_str; //浅拷贝,引用计数++
29 IncreaseRefCount();
30 }
31 return *this;
32 }
33 private:
34 //CharProxy中去重载=与<<运算符,争对读写有不同的操作
35 class CharProxy{
36 public:
37 CharProxy(String &self, size_t idx):_self(self), _idx(idx){
38
39 }
40 //写操作
41 char &operator=(const char &ch); //string是不完整类型,所以要在类外实现
42 //读操作
43 friend std::ostream &operator<<(std::ostream &os, const CharProxy &rhs);
44
45 private:
46 String &_self; //CharProxy在string里面写,是不完整类型,无法创建对象
47 size_t _idx; //self找到m_pstr,可以去下标,去除string中每一个字符
48 };
49
50 public:
51
52 CharProxy operator[](size_t idx){
53 return CharProxy(*this, idx); //临时对象,所以不能返回引用
54 }
55
56 #if 0 这样去重载[],无法区分读写操作 若对s1[0]进行读,依然会修改引用计数
57 char &operator[](size_t idx){
58 if(idx < size()){
59 if(getRefcount() > 1){ //共享空间,[idx]修改时进行深拷贝
60 char *tmp = new char[size() + 5]() + 4; //深拷贝
61 strcpy(tmp, m_str);
62 DecreaseRefCount();
63 m_str = tmp; //浅拷贝
64 InitRefCount();
65 }
66 return m_str[idx];
67 }else{
68 static char charNull = '\0';
69 return charNull;
70 }
71 }
72 #endif
73 ~String(){
74
75 DecreaseRefCount();
76 if(0 == getRefcount()){
77
78 delete [] (m_str - 4);
79 }
80 }
81
82 int getRefcount() const{ //获取引用计数
83 return *(int*)(m_str - 4);
84 }
85
86 const char* c_str() const{
87 return m_str;
88 }
89
90 size_t size() const{
91 return strlen(m_str);
92 }
93
94 friend std::ostream &operator<<(std::ostream &os, const String::CharProxy &rhs);
95 friend std::ostream &operator<<(std::ostream &os, const String &rhs);
96 private:
97 char *m_str;
98 /* static int refCount; //静态变量为所有对象所共享,无法表示不同对象的引用计数 */
99
100 void InitRefCount(){ //初始化引用计数
101 *(int*)(m_str - 4) = 1;
102 }
103
104 void IncreaseRefCount(){ //引用计数++
105 ++*(int*)(m_str - 4);
106 }
107
108 void DecreaseRefCount(){ //引用计数--
109 --*(int*)(m_str - 4);
110 }
111 };
112
113
114 std::ostream &operator<<(std::ostream &os, const String &rhs){
115 if(rhs.m_str){
116 os << rhs.m_str;
117 }
118 return os;
119 }
120
121 //写操作
122 char &String:: CharProxy::operator=(const char &ch){
123 if(_idx < _self.size()){
124 if(_self.getRefcount() > 1){ //共享空间,[idx]修改时进行深拷贝
125 char *tmp = new char[_self.size() + 5]() + 4; //深拷贝
126 strcpy(tmp, _self.m_str);
127 _self.DecreaseRefCount();
128 _self.m_str = tmp; //浅拷贝
129 _self.InitRefCount();
130 }
131 _self.m_str[_idx] = ch; //真正的写操作
132 return _self.m_str[_idx];
133 }else{
134 static char charNull = '\0';
135 return charNull;
136 }
137
138 }
139
140 std::ostream &operator<<(std::ostream &os, const String::CharProxy &rhs){
141 os << rhs._self.m_str[rhs._idx];
142 return os;
143 }
- 关于重载 [ ]运算符遇到的问题
为了区分下标访问运算符的读写操作,s3 [0] = ' H ' cout << s1 [ 0 ] << endl ; 所以需要对 = 与 << 进行重载,重载运算符时,必须有一个是类类型,所以再写一个类 CharProxy ,在该类中重载
把下标访问运算符中的返回类型由 char & 变为 CharProxy
CharProxy中为了操作String中的一个个字符,用到数据成员 String &(用引用是操作的String本身且String为不完整类型)与 size_t
之后对写操作与读操作进行重载,因为String在CharProxy中是不完整类型,所以要在类外实现
测试代码:
1 void test(){
2
3 String s1("hello");
4 cout << "s1 = " << s1 << endl;
5 cout << "s1.getRefcount()" << s1.getRefcount() << endl;
6 printf("s1 address is %p\n", s1.c_str());
7
8 cout << endl << endl;
9 String s2 = s1;
10 cout << "s1 = " << s1 << endl;
11 cout << "s2 = " << s2 << endl;
12 cout << "s1.getRefcount()" << s1.getRefcount() << endl;
13 cout << "s2.getRefcount()" << s2.getRefcount() << endl;
14 printf("s1 address is %p\n", s1.c_str());
15 printf("s2 address is %p\n", s2.c_str());
16
17 cout << endl << endl;
18 String s3("world");
19 cout << "s3" << s3 << endl;
20 cout << "s3.getRefcount()" << s3.getRefcount() << endl;
21 printf("s3 address is %p\n", s3.c_str());
22
23 cout << endl << endl;
24 s3 = s1;
25 cout << "s1 = " << s1 << endl;
26 cout << "s2 = " << s2 << endl;
27 cout << "s3 = " << s3 << endl;
28 cout << "s1.getRefcount()" << s1.getRefcount() << endl;
29 cout << "s2.getRefcount()" << s2.getRefcount() << endl;
30 cout << "s3.getRefcount()" << s3.getRefcount() << endl;
31 printf("s1 address is %p\n", s1.c_str());
32 printf("s2 address is %p\n", s2.c_str());
33 printf("s3 address is %p\n", s3.c_str());
34
35 cout << endl << "对s3执行写操作" << endl;
36 //s3.operator[](idx)
37 //CharProxy = char;
38 s3[0] = 'H';
39 cout << "s1 = " << s1 << endl;
40 cout << "s2 = " << s2 << endl;
41 cout << "s3 = " << s3 << endl;
42 cout << "s1.getRefcount()" << s1.getRefcount() << endl;
43 cout << "s2.getRefcount()" << s2.getRefcount() << endl;
44 cout << "s3.getRefcount()" << s3.getRefcount() << endl;
45 printf("s1 address is %p\n", s1.c_str());
46 printf("s2 address is %p\n", s2.c_str());
47 printf("s3 address is %p\n", s3.c_str());
48
49 cout << endl << "对s1[0]执行读操作" << endl;
50 //cout << CharProxy
51 cout << "s1[0] = " << s1[0] << endl;
52 cout << "s1 = " << s1 << endl;
53 cout << "s2 = " << s2 << endl;
54 cout << "s3 = " << s3 << endl;
55 cout << "s1.getRefcount()" << s1.getRefcount() << endl;
56 cout << "s2.getRefcount()" << s2.getRefcount() << enl;
57 cout << "s3.getRefcount()" << s3.getRefcount() << endl;
58 printf("s1 address is %p\n", s1.c_str());
59 printf("s2 address is %p\n", s2.c_str());
60 printf("s3 address is %p\n", s3.c_str());
61 }
3、短字符串优化
核心思想:发生拷贝时要复制一个指针,对小字符串来说,为啥不直接复制整个字符串呢,说不定还没有复制一个指针的代价大(小字符串复制指针,大字符串复制字符串)
C++Day09 深拷贝、写时复制(cow)、短字符串优化的更多相关文章
- Rust写时复制Cow<T>
写时复制(Copy on Write)技术是一种程序中的优化策略,多应用于读多写少的场景.主要思想是创建对象的时候不立即进行复制,而是先引用(借用)原有对象进行大量的读操作,只有进行到少量的写操作的时 ...
- c++ string写时复制
string写时复制:将字符串str1赋值给str2后,除非str1的内容已经被改变,否则str2和str1共享内存.当str1被修改之后,stl才为str2开辟内存空间,并初始化. #include ...
- JAVA中写时复制(Copy-On-Write)Map实现
1,什么是写时复制(Copy-On-Write)容器? 写时复制是指:在并发访问的情景下,当需要修改JAVA中Containers的元素时,不直接修改该容器,而是先复制一份副本,在副本上进行修改.修改 ...
- fork()和写时复制
写时复制技术最初产生于Unix系统,用于实现一种傻瓜式的进程创建:当发出fork( )系统调用时,内核原样复制父进程的整个地址空间并把复制的那一份分配给子进程.这种行为是非常耗时的,因为它需要: · ...
- Linux的fork()写时复制原则(转)
写时复制技术最初产生于Unix系统,用于实现一种傻瓜式的进程创建:当发出fork( )系统调用时,内核原样复制父进程的整个地址空间并把复制的那一份分配给子进程.这种行为是非常耗时的,因为它需要: · ...
- Linux进程管理——fork()和写时复制
写时复制技术最初产生于Unix系统,用于实现一种傻瓜式的进程创建:当发出fork( )系统调用时,内核原样复制父进程的整个地址空间并把复制的那一份分配给子进程.这种行为是非常耗时的,因为它需要: · ...
- 写时复制和fork,vfork,clone
写时复制 原理: 用了“引用计数”,会有一个变量用于保存引用的数量.当第一个类构造时,string的构造函数会根据传入的参数从堆上分配内存,当有其它类需要这块内存时,这个计数为自动累加,当有类析构时, ...
- Java进阶知识点6:并发容器背后的设计理念 - 锁分段、写时复制和弱一致性
一.背景 容器是Java编程中使用频率很高的组件,但Java默认提供的基本容器(ArrayList,HashMap等)均不是线程安全的.当容器和多线程并发编程相遇时,程序员又该何去何从呢? 通常有两种 ...
- 用户空间缺页异常pte_handle_fault()分析--(下)--写时复制【转】
转自:http://blog.csdn.net/vanbreaker/article/details/7955713 版权声明:本文为博主原创文章,未经博主允许不得转载. 在pte_handle_fa ...
- Redis持久化之父子进程与写时复制
之所以将Linux底层的写时复制技术放在Redis篇幅下,是因为Redis进行RDB持久化时,BGSAVE(后面称之为"后台保存")会开辟一个子进程,将数据从内存写进磁盘,这儿我产 ...
随机推荐
- 人人都懂的HTML基础知识-HTML教程(1)
01.HTML基础简介 HTML (HyperText Markup Language,超文本标记语言) 不是一门编程语言,而是一种用于定义内容结构的标记语言,用来描述网页内容,文件格式为.html. ...
- vscode删除卸载残余
1.删除安装插件C:/User/XXX/.vscode2.删除用户信息和缓存信息C:/User/XXX/AppData/Roaming/CodeC:/User/XXX/AppData/Roaming/ ...
- 小米MIUI禁止系统更新
删除downloaded_rom的文件夹,随便找一个文件(文件,不是文件夹),重名为downloaded_rom(是把一个文件重命名),这样系统后台偷偷下载时,就不知道该存放更新包的文件,就无法偷偷更 ...
- Codeforces Round #829 (Div. 2)/CodeForces1754
CodeForces1754 注:所有代码均为场上所书 Technical Support 解析: 题目大意 给定一个只包含大写字母 \(\texttt{Q}\) 和 \(\texttt{A}\) 的 ...
- 测试架构师CAP原理(最简单)
测试架构师CAP原理(最简单) 很多人都不是很了解CAP理论,其实CAP很简单,不要想复杂了! C:一致性,就是数据一致性,就是数据不出错! A:可用性,就是说速度快,不延迟,无论请求成功失败都很快返 ...
- photoshop 2021 for mac安装教程,亲测可用!!!
小编分享下photoshop cc 2021 for mac 安装教程,适配M1芯片,让大家完美使用ps2021,畅享所有新功能Adobe Photoshop2021(简称PS) 新版本主要增加了Ne ...
- 基于PCIe的多路视频采集与显示子系统
基于PCIe的多路视频采集与显示子系统 1 概述 视频采集与显示子系统可以实时采集多路视频信号,并存储到视频采集队列中,借助高效的硬实时视频帧出入队列管理和PCIe C2H DMA引擎, ...
- Substring 在BCL和CLR里面搞了啥
楔子 还是做点事情,不要那么散漫. 本文以简单的Substring(int startindex,int Length)函数为例,来递进下它在托管和非托管的一些行为. 以下均为个人理解,如有疏漏请指正 ...
- Java-(array)数组的基本概念 及 Java内存划分
(array)数组的基本概念 数组的概念:是一种容器,可同时存放多个数据值 数组的特点: 1.数组是一种引用数据类型 2.数组当中的多个数据,类型必须统一 3.数组的长度在程序运行期间不可改变 数组的 ...
- 在业务逻辑中,经常会有父组件调用子组件方法的情况,vue2.0 和 vue3.0 的使用中有些不一样,在这里总结下。
在业务逻辑中,经常会有父组件调用子组件方法的情况,vue2.0 和 vue3.0 的使用中有些不一样,在这里总结下. vue2.0 中的使用方法 父组件: <template> <d ...