http://blog.51cto.com/zgw285763054/1839752

维基百科:

写入时复制(英语:Copy-on-write,简称COW)是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者(callers)同时要求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此作法主要的优点是如果调用者没有修改该资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

String类中的写时拷贝技术是指用浅拷贝的方法拷贝其他对象,多个指针指向同一块空间,只有当对其中一个对象修改时,才会开辟一个新的空间给这个对象,和它原来指向同一空间的对象不会受到影响。写时拷贝的效率远高于深拷贝。

可以通过增加一个成员变量count来实现写时拷贝,这个变量叫做引用计数,统计这块空间被多少个对象的_str同时指向。当用指向这块空间的对象拷贝一个新的对象出来时count+1,当指向这块空间的一个对象指向别的空间或析构时count-1。只有当count等于0时才可以释放这块空间,否则说明还有其他对象指向这块空间,不能释放。

count应该是什么类型呢?如果是int类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class String
{
    public:
    String(const char* str)
        :_str(new char[strlen(str)+1])
        ,_count(1)
    {
        strcpy(_str, str);
    }
    String(String& s)
        :_str(s._str)
    {
        ++s._count;
        _count = s._count;
    }
    ~String()
    {
        if (--_count == 0)
        {
            delete[] _str;
        }
    }
private:
    char* _str;
    int _count;
};
void Test()
{
    String s1("aaaaaaaaa");
    String s2(s1);
}

虽然s1._count和s2._count都等于2,但是当s2执行析构函数后

现在只剩下s1一个对象指向这块空间,s1._count和s2._count应该都变为1,但是s1._count没有改变,查看s1._count和s2._count的地址发现它们并不是同一个地址,改变count只对当前对象有效,其他对象不会受到影响,无法实现引用计数。

这说明count是公共的,可以被多个对象同时访问的。如果是static int类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class String
{
    public:
    String(const char* str)
        :_str(new char[strlen(str)+1])
    {
        _count = 1;
        strcpy(_str, str);
    }
    String(String& s)
        :_str(s._str)
    {
        ++_count;
    }
    ~String()
    {
        if (--_count == 0)
        {
            delete[] _str;
        }
    }
private:
    char* _str;
    static int _count;
};
int String::_count = 0;
void Test()
{
    String s1("aaaaaaaaa");
    String s2(s1);
    String s3(s2);
    String s4("bbbbbbbbb");
    String s5(s4);
}

现在s1、s2、s3的引用计数应该是3,s4、s5的引用计数应该是2。

但是结果不正确。原因是s1、s2、s3指向同一块空间后count增加到3,构造s4时又把count设置为1,s4拷贝构造s5后count增加到2。说明这5个对象共用一个count,不能实现引用计数。

如果一个对象第一次开辟空间存放字符串时再开辟一块新的空间存放引用计数,当它拷贝构造其他对象时让其他对象的引用计数都指向存放引用计数的同一块空间,count设置为int*类型,就可以实现引用计数了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class String
    {
    public:
        String(const char* str)
            :_str(new char[strlen(str)+1])
            ,_pCount(new int(1))
        {
            strcpy(_str, str);
        }
        String(String& s)
            :_str(s._str)
            ,_pCount(s._pCount)
        {
            ++(*_pCount);
        }
        String& operator=(const String& s)
        {
            if (/*this != &s ||*/ _str != s._str) //防止自己给自己赋值,或自己拷贝的对象给自己赋值
            {
                //释放原对象
                if (--(*_pCount) == 1)
                {
                    delete _pCount;
                    delete[] _str;
                }
                //浅拷贝增加引用计数
                _str = s._str;
                _pCount = s._pCount;
                ++(*_pCount);
            }
            return *this;
        }
        ~String()
        {
            if (--*_pCount == 0)
            {
                delete _pCount;
                delete[] _str;
            }
        }
    protected:
        char* _str;
        int* _pCount;
    };

但是这种方法也存在不足:

1、它每次new两块空间,创建多个对象时效率较低于下面这种方法;

2、它多次分配小块空间,容易造成内存碎片化,导致分配不出来大块内存。

还有一种方法是在开辟_str时多开辟4个字节,在这块空间的头部保存引用计数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class String
{
public:
    String(const char* str)
        :_str(new char[strlen(str)+5])
    {
        _str += 4;
        strcpy(_str, str);
        //(*(int*)(_str-4)) = 1;
        _GetRefCount(_str) = 1;
    }
    String(const String& s)
        :_str(s._str)
    {
        //*((int*)(_str-4)) += 1;
        _GetRefCount(_str)++;
    }
    String& operator=(const String& s)
    {
        if (/*this != &s ||*/ _str != s._str) //防止自己给自己赋值,或自己拷贝的对象给自己赋值
        {
            /*if (--(*(int*)(_str-4)) == 0)
            {
                delete[] (_str-4);
            }*/
            _Release();
            _str = s._str;
            ++(*(int*)(s._str-4));
        }
        return *this;
    }
    ~String()
    {
        /*if (--(*(int*)(_str-4)) == 0)
        {
            delete[] (_str-4);
        }*/
        _Release();
    }
    //operator[]的特殊性,读时也拷贝
    char& operator[](size_t index)
    {
        //当引用计数大于1,需要写时拷贝
        if (_GetRefCount(_str) > 1)
        {
            char* tmp = new char[strlen(_str) + 5];
            --_GetRefCount(_str); //new空间后再减引用计数,防止new空间失败
            tmp += 4;
            _GetRefCount(tmp) = 1;
            _str = tmp;
        }
        return _str[index];
    }
protected:
    int& _GetRefCount(char* _ptr)
    {
        return *((int*)(_ptr-4));
    }
    //--引用计数,如果引用计数等于0,释放
    void _Release()
    {
        if (/*--(*(int*)(_str-4))*/--_GetRefCount(_str) == 0)
        {
            delete[] (_str-4);
        }
    }
protected:
    char* _str;
};

1
2
3
4
5
6
7
8
9
10
11
12
void COWTest()
    {
        String s1("aaaaaaaaaaa");
        String s2(s1);
        String s3(s1);
        //operator[]的特殊性,读时也拷贝
        cout<<s1[0]<<endl;
         
        //写时拷贝
        s1[0] = '1';
    }

当对s1修改后,s1指向新拷贝出来的空间。

推荐文章:  《C++ STL string的Copy-On-Write技术》

C++ String类写时拷贝 4相关推荐

  1. C++:String的写时拷贝

    String的写时拷贝 //test.h #pragma once#include <iostream> #include <string.h> #include <cs ...

  2. 写时拷贝技术(COW)

    文章目录 写时拷贝技术 写时拷贝技术原理 举个例子 写时拷贝技术   写时拷贝技术实际上是一种拖延战术,是为了提高效率而产生的技术,这怎么提高效率呢?实际上就是在需要开辟空间时,假装开了空间,实际上用 ...

  3. 标准C++类std::string的内存共享和Copy-On-Write(写时拷贝)

    标准C++类std::string的内存共享,值得体会: 详见大牛:https://www.douban.com/group/topic/19621165/ 顾名思义,内存共享,就是两个乃至更多的对象 ...

  4. string类的写时拷贝

    由于浅拷贝使多个对象共用一块内存地址,调用析构函数时导致一块内存被多次释放,导致程序奔溃. 实现string类的时候通常显示的定义拷贝构造函数和运算符重载函数. 由于释放内存空间,开辟内存空间时花费时 ...

  5. c++ string类深拷贝其他版本(简洁版,引用计数版,写时拷贝版)

    简洁版: class String { public:String(char* pStr=""){if(pStr==NULL){_pStr=new char[0];_pStr='\ ...

  6. 浅拷贝+引用计数--写时拷贝---模拟实现string容器

    引用计数 深拷贝 多个对象共享同一份资源时,最后能够保证该资源只被释放一次 应该由哪个对象释放资源? 由最后一个使用该资源的对象去释放 怎么知道一个对象是最后一个使用该资源的对象? 给一个计数,记录使 ...

  7. String写时拷贝实现

    头文件部分 1 /* 2 版权信息:狼 3 文件名称:String.h 4 文件标识: 5 摘 要:对于上版本简易的String进行优化跟进. 6 改进 7 1.(将小块内存问题与大块分别对待)小内存 ...

  8. 写时复制,写时拷贝,写时分裂,Copy on write

    2019独角兽企业重金招聘Python工程师标准>>> 写时复制,写时拷贝,写时分裂 (Copy-on-write,简称COW)是计算机资源管理方面的一种优化技术,有着广泛的应用,比 ...

  9. php zval_copy_static_var(),zval _ 引用计数 _ 变量分离 _ 写时拷贝

    zval.引用计数.变量分离.写时拷贝 我们一步步来理解 1.php语言特性 PHP是脚本语言,所谓脚本语言,就是说PHP并不是独立运行的,要运行PHP代码需要PHP解析器,用户编写的PHP代码最终都 ...

最新文章

  1. treeview 保持选中状态
  2. DELL equallogic 的MPIO配置
  3. js字符串的字典序_JavaScript通过字典进行字符串翻译转换的方法
  4. Cortex-M3-建立向量表
  5. 干掉ubuntu中的overlay scrollbar
  6. [emacs] 用 ido 或 anything 进行选择
  7. 重磅揭晓阿里 AliOS Things 3.0 革命性创新!
  8. 《变革中的思索》连载二:依旧有效的四律
  9. 信噪比计算方式(小问题解惑)
  10. FL Studio使用外部音源播放导入的MIDI文件
  11. 好用的3D建模软件,就是不用?
  12. 三、Redis在SpringBoot中使用案例
  13. 汽车功能安全工程师必看!ISO 26262认证基本原理解析
  14. easyUI折叠表格-默认展开操作-去除扩展符号(+)-清除滚轮--实例加效果图
  15. 【荐藏】代谢组学分析系列
  16. 基于vtkVoxelContoursToSurfaceFilter实现勾画图像的轮廓线面重建
  17. wireshark ARP抓包
  18. 数据智能公司袋鼠云完成 6000 万元 A 轮融资
  19. 上海科技大学和南方科技大学计算机,上海科技大学和南方科技大学哪个好?哪个更容易考?...
  20. 教资 2022(上) | 小学科目三- 小学教师资格证面试(教学技巧)考情考点梳理

热门文章

  1. 网站上flv,MP4等格式的视频文件播放不出来的解决办法
  2. activemq安装与配置_ActiveMQ(消息队列)从入门到了解
  3. aix系统java堆_浅谈AIX环境下的Java性能调优
  4. python自带sqlite库_Python标准库之sqlite3使用实例
  5. linux7系统如何配置网卡,Linux 7 配置网卡(nmcli)
  6. mysql主从配置错误_mysql主从配置失败,主从通讯失败
  7. python 复制文件夹内容 并结构一致_Python比较文件夹比另一同名文件夹多出的文件并复制出来的方法...
  8. OJ RuntimeError常见原因
  9. Win32ASM学习[15]:加减指令: INC、DEC、NEG、ADD、ADC、SUB、SBB、CMP
  10. 通过查看__cplusplus的值查看编译器的C++标准