C++Day09 深拷贝、写时复制(cow)、短字符串优化

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

一、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、短字符串优化

核心思想:发生拷贝时要复制一个指针,对小字符串来说,为啥不直接复制整个字符串呢,说不定还没有复制一个指针的代价大(小字符串复制指针,大字符串复制字符串)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: c++

“C++Day09 深拷贝、写时复制(cow)、短字符串优化” 的相关文章