一、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)、短字符串优化的更多相关文章

  1. Rust写时复制Cow<T>

    写时复制(Copy on Write)技术是一种程序中的优化策略,多应用于读多写少的场景.主要思想是创建对象的时候不立即进行复制,而是先引用(借用)原有对象进行大量的读操作,只有进行到少量的写操作的时 ...

  2. c++ string写时复制

    string写时复制:将字符串str1赋值给str2后,除非str1的内容已经被改变,否则str2和str1共享内存.当str1被修改之后,stl才为str2开辟内存空间,并初始化. #include ...

  3. JAVA中写时复制(Copy-On-Write)Map实现

    1,什么是写时复制(Copy-On-Write)容器? 写时复制是指:在并发访问的情景下,当需要修改JAVA中Containers的元素时,不直接修改该容器,而是先复制一份副本,在副本上进行修改.修改 ...

  4. fork()和写时复制

    写时复制技术最初产生于Unix系统,用于实现一种傻瓜式的进程创建:当发出fork(  )系统调用时,内核原样复制父进程的整个地址空间并把复制的那一份分配给子进程.这种行为是非常耗时的,因为它需要: · ...

  5. Linux的fork()写时复制原则(转)

    写时复制技术最初产生于Unix系统,用于实现一种傻瓜式的进程创建:当发出fork(  )系统调用时,内核原样复制父进程的整个地址空间并把复制的那一份分配给子进程.这种行为是非常耗时的,因为它需要: · ...

  6. Linux进程管理——fork()和写时复制

    写时复制技术最初产生于Unix系统,用于实现一种傻瓜式的进程创建:当发出fork(  )系统调用时,内核原样复制父进程的整个地址空间并把复制的那一份分配给子进程.这种行为是非常耗时的,因为它需要: · ...

  7. 写时复制和fork,vfork,clone

    写时复制 原理: 用了“引用计数”,会有一个变量用于保存引用的数量.当第一个类构造时,string的构造函数会根据传入的参数从堆上分配内存,当有其它类需要这块内存时,这个计数为自动累加,当有类析构时, ...

  8. Java进阶知识点6:并发容器背后的设计理念 - 锁分段、写时复制和弱一致性

    一.背景 容器是Java编程中使用频率很高的组件,但Java默认提供的基本容器(ArrayList,HashMap等)均不是线程安全的.当容器和多线程并发编程相遇时,程序员又该何去何从呢? 通常有两种 ...

  9. 用户空间缺页异常pte_handle_fault()分析--(下)--写时复制【转】

    转自:http://blog.csdn.net/vanbreaker/article/details/7955713 版权声明:本文为博主原创文章,未经博主允许不得转载. 在pte_handle_fa ...

  10. Redis持久化之父子进程与写时复制

    之所以将Linux底层的写时复制技术放在Redis篇幅下,是因为Redis进行RDB持久化时,BGSAVE(后面称之为"后台保存")会开辟一个子进程,将数据从内存写进磁盘,这儿我产 ...

随机推荐

  1. Python爬虫requests请求库

    requests:pip install  request 安装 实例: import requestsurl = 'http://www.baidu.com'response = requests. ...

  2. TCP 序列号和确认号是如何变化的?

    大家好,我是小林. 在网站上回答了很多人的问题,我发现很多人对 TCP 序列号和确认号的变化都是懵懵懂懂的,只知道三次握手和四次挥手过程中,ACK 报文中确认号要 +1,然后数据传输中 TCP 序列号 ...

  3. packet Capture 手机抓包工具

    packet Capture packet Capture 是一款免root的app, 运行在安卓平台上,用于捕获http/https网络流量嗅探的应用程序 特点: 捕获网络数据包,并记录太慢,使用中 ...

  4. ubuntu 安装anaconda3

    ubuntu 安装anaconda3 官网:https://www.anaconda.com/ 下载:https://www.anaconda.com/products/individual#Down ...

  5. Websocket集群解决方案

    最近在项目中在做一个消息推送的功能,比如客户下单之后通知给给对应的客户发送系统通知,这种消息推送需要使用到全双工的websocket推送消息. 所谓的全双工表示客户端和服务端都能向对方发送消息.不使用 ...

  6. C#使用附加到进程调试

    微软官网的调试进程介绍 首先运行bin下的可执行文件,然后打开源代码,选择调试--->附加到进程.

  7. Go语言核心36讲17

    在前面的文章中,我们已经提到过很多次"指针"了,你应该已经比较熟悉了.不过,我们那时大多指的是指针类型及其对应的指针值,今天我们讲的则是更为深入的内容. 让我们先来复习一下. ty ...

  8. 2.3 Goland快捷键

    1 新建 Alt + Insert Alt + Enter 2 移动 Alt + 上下箭头:以函数为单位移动 Ctrl + G : 定位到行 Ctrl + W :可以选择单词继而语句继而行继而函数Ct ...

  9. Linux *.service文件详解

    什么是systemd service? systemd service是一种以.service 结尾的配置文件,是一个专用于Linux操作系统的系统与服务管理器.简单来说,用于后台以守护精灵(daem ...

  10. MyBatis-Plus 分页插件过时

    引用:https://blog.csdn.net/zyw562123314/article/details/108903456//分页插件老版本过时 旧版本配置 @Bean public Pagina ...