草庐IT

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

YongSir 2023-03-28 原文

一、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. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  3. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  4. ruby-on-rails - unicode 字符串的长度 - 2

    在我的Rails(2.3,Ruby1.8.7)应用程序中,我需要将字符串截断到一定长度。该字符串是unicode,在控制台中运行测试时,例如'א'.length,我意识到返回了双倍长度。我想要一个与编码无关的长度,以便对unicode字符串或latin1编码字符串进行相同的截断。我已经了解了Ruby的大部分unicode资料,但仍然有些一头雾水。应该如何解决这个问题? 最佳答案 Rails有一个返回多字节字符的mb_chars方法。试试unicode_string.mb_chars.slice(0,50)

  5. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  6. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  7. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

    我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

  8. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  9. ruby - 如何使用文字标量样式在 YAML 中转储字符串? - 2

    我有一大串格式化数据(例如JSON),我想使用Psychinruby​​同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解

  10. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

随机推荐