一、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. 文本转语音TTS(文本阅读和视频配音制作)MP3

    DL-TTS 通过AI驱动引擎可将文本转化为逼真的语音,它可以:(1)生成逼真的合成语音实现与人声的语调和情感匹配的流畅.发音自然的文本转语音.(2)细化的文本转语音控制支持多种语言,并可调整语速.语 ...

  2. C语言基础--数组

    数组 概念:在内存中连续存储的具有相同数据类型的一组数据的集合. 注意: 数组中的数据类型必须都是一致的 数组在内存中必须是连续的存储空间 定义数组时候的注意事项: 定义数组的时候,[]里面的值不能是 ...

  3. Halocn双目相机标定

    [Halcon]Halcon双目标定 相机标定(4)---基于halcon的双目立体视觉标定 双目立体视觉:四(双目标定matlab,图像校正,图像匹配,计算视差,disparity详解,) 双目测距 ...

  4. mybatis中association和collection使用

    mybatis中association和collection使用 一.概述 association:一个复杂的类型关联.许多结果将包成这种类型 collection:复杂类型的集合 这2个属性的使用, ...

  5. ftp多用户多目录配置

    测试环境:centos7 1. 装包与卸载 yum -y install vsftpd yum -y autoremove vsftpd&&rm -rf /etc/vsftpd /et ...

  6. kubernetes笔记-1-基础环境部署

    一.环境信息: 操作系统:ubuntu 18.04 server amd64 docker:docker 19.03.ce kubernetes:v1.19 IP地址 主机名   角色 172.29. ...

  7. (C++) 类与 static_cast 与 dynamic_cast

    static_cast static_cast相当于C语言里面的强制转换,适用于: 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换.进行上行转换(把派生类的指针或引用转换成基类表示) ...

  8. 这可能是最全的SpringBoot3新版本变化了!

    11月24号,Spring Boot 3.0 发布了第一个正式的 GA 版本,一起看看新版本到底有哪些变化. 2.7版本升级指南 官方提供了一个从 2.7 版本升级到 3.0 的指南:https:// ...

  9. Flink同步Kafka数据到ClickHouse分布式表

    公众号文章都在个人博客网站:https://www.ikeguang.com/ 同步,欢迎访问. 业务需要一种OLAP引擎,可以做到实时写入存储和查询计算功能,提供高效.稳健的实时数据服务,最终决定C ...

  10. 【Java SE进阶】Day05 异常,线程

    一.异常 1.概念 程序执行过程中,出现非正常情况导致JVM的非正常停止 本身是一个类,产生异常即创建并抛出一个异常对象 Java处理异常的方式是进行中断处理 异常非语法错误,语法错误直接不会产生cl ...