C++ String类写时拷贝 4

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执行析构函数后

wKiom1e0lYnj-bOGAABTs5LqMs8002.png-wh_50

    现在只剩下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。

wKioL1e0mMPhC5sgAACS0fxKwZk012.png-wh_50


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

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

wKiom1e1IQWjg4hxAAA36elJxOg735.png-wh_50

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个字节,在这块空间的头部保存引用计数。

    wKioL1e1IlSyOZsyAAAo9y7E_Zg815.png-wh_50


    

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指向新拷贝出来的空间。

wKiom1e1MhPB-qKzAAAvTg48J2Q244.png-wh_50


wKioL1e1Mh7jx0EUAABJ_CjTlQ4677.png-wh_50


wKioL1e1NjrRyOI-AAA-HOpOlFw930.png-wh_50


wKioL1e1Npzx5JikAAA9nwxQX3c473.png-wh_50


    

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


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383776.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++笔试复习

基础知识点 C中对象数组在定义的时候全部进行实例化&#xff08;与Java不同&#xff0c;Java相当于只是定义了一个指针数组&#xff0c;没有进行实例化&#xff09; 程序的三种基本控制结构是&#xff1a;顺序结构、循环结构、选择结构 一个C程序开发步骤通常包括&#xff1a…

Java笔试复习

Java程序运行 Java程序的执行必须经过编辑、编译和运行三个步骤 编辑指编写代码&#xff0c;最终形成后缀名为.java的Java源文件编译指使用Java编译器&#xff08;javac指令&#xff09;将源文件翻译为二进制代码&#xff0c;编译后生成后缀名为.class的字节码文件&#xff0c…

Java类名与包名不区分大小写

刚才写了一个简单的Java程序&#xff0c;经过测试得到一个令人震惊的结论&#xff1a;Java类名和包名是不区分大小写的 可以看一下这个例子&#xff1a; package Test;class aBcdEfG {}class AbCdefg {}public class TTT {public static void main(String[] args){AbCdefg tm…

处理大并发之一 对epoll的理解,epoll客户端服务端代码

http://blog.csdn.net/zhuxiaoping54532/article/details/56494313处理大并发之一对epoll的理解&#xff0c;epoll客户端服务端代码序言&#xff1a;该博客是一系列的博客&#xff0c;首先从最基础的epoll说起&#xff0c;然后研究libevent源码及使用方法&#xff0c;最后研究n…

链栈基本操作

http://blog.csdn.net/jwentao01/article/details/46765517###;栈基本概念&#xff1a; 栈&#xff08;stack&#xff09;是限定在表尾进行插入和删除操作的线性表&#xff08;或单链表&#xff09;。 //只能在一段进行插入和删除&#xff0c;因此不存在&#xff0c;在中间进行…

Linux网络编程---I/O复用模型之select

https://blog.csdn.net/men_wen/article/details/53456435Linux网络编程—I/O复用模型之select 1. IO复用模型 IO复用能够预先告知内核&#xff0c;一旦发现进程指定的一个或者多个IO条件就绪&#xff0c;它就通知进程。IO复用阻塞在select或poll系统调用上&#xff0c;而不是阻…

Linux网络编程---I/O复用模型之poll

https://blog.csdn.net/men_wen/article/details/53456474Linux网络编程—I/O复用模型之poll 1.函数poll poll系统调用和select类似&#xff0c;也是在指定时间内轮询一定数量的文件描述符&#xff0c;以测试其中是否有就绪者。 #include <poll.h>int poll(struct pollfd…

Linux网络编程---I/O复用模型之epoll

https://blog.csdn.net/men_wen/article/details/53456474 Linux网络编程—I/O复用模型之epoll 1. epoll模型简介 epoll是Linux多路服用IO接口select/poll的加强版&#xff0c;e对应的英文单词就是enhancement&#xff0c;中文翻译为增强&#xff0c;加强&#xff0c;提高&…

POJ 1741tree-点分治入门

学习了一下点分治&#xff0c;如果理解有误还请不吝赐教。 为了快速求得树上任意两点之间距离满足某种关系的点对数&#xff0c;我们需要用到这种算法。 点分治是树上的一种分治算法&#xff0c;依靠树和子树之间的关系进行分治从而降低复杂度。 和其他树上的算法有一些区别…

基于单链表的生产者消费者问题

『生产者与消费者问题分析』「原理」生产者生产产品&#xff0c;消费者消费产品。产品如果被消费者消费完了&#xff0c;同时生产者又没有生产出产品&#xff0c;消费者 就必须等待。同样的&#xff0c;如果生产者生产了产品&#xff0c;而消费者没有去消费&#x…

C++智能指针(二)模拟实现三种智能指针

https://blog.csdn.net/nou_camp/article/details/70186721在上一篇博客中提到了Auto_ptr(C智能指针&#xff08;一&#xff09;)&#xff0c;下面进行模拟实现Auto_ptr 采用类模板实现 #include<iostream> using namespace std; template<class T> class Autoptr …

C++智能指针(三)总结

https://blog.csdn.net/nou_camp/article/details/70195795 在上一篇博客中&#xff08;C智能指针&#xff08;二&#xff09;&#xff09;模拟实现了三种智能指针。 其中最好的就是shared_ptr,但是这并不代表它就是最完美的&#xff0c;它也有问题&#xff0c;这个问题就是循环…

c++11 你需要知道这些就够了

https://blog.csdn.net/tangliguantou/article/details/50549751c11新特性举着火把寻找电灯今天我就权当抛砖引玉&#xff0c;如有不解大家一起探讨。有部分内容是引用自互联网上的内容&#xff0c;如有问题请联系我。T&& 右值引用 std::move 右值引用出现之前我们只能…

c++仿函数 functor

https://www.cnblogs.com/decade-dnbc66/p/5347088.html内容整理自国外C教材先考虑一个简单的例子&#xff1a;假设有一个vector<string>&#xff0c;你的任务是统计长度小于5的string的个数&#xff0c;如果使用count_if函数的话&#xff0c;你的代码可能长成这样&#…

Ubuntu软件更新失败

刚安装好Ubuntu以后需要将系统的软件都更新一下&#xff0c;但是遇到一个问题就是下载仓库信息失败&#xff0c;大概是这个样子的错误&#xff1a; 经国遇到这样的问题可以试一下下面这个命令&#xff1a; sudo rm -rf /var/lib/apt/lists/* sudo apt-get update参考网址&…

getsockname函数与getpeername函数的使用

https://www.tuicool.com/articles/V3Aveygetsockname和getpeername函数 getsockname函数用于获取与某个套接字关联的本地协议地址 getpeername函数用于获取与某个套接字关联的外地协议地址 定义如下&#xff1a;[cpp] view plaincopy#include<sys/socket.h> int gets…

Linux命令【一】基本命令

shell命令和bash命令相同&#xff0c;指的是命令解析器 快捷键 history 所有的历史命令ctrl P 向上滚动命令 ctrl N 向下滚动命令 ctrlB将光标向前移动 ctrlF将光标向后移动 ctrlA移动到命令行头部 ctrlE移动到命令行尾部 光标删除操作&#xff1a;删除光标前面字符ctrlh或…

剑指offer面试题:替换空格

https://blog.csdn.net/yanxiaolx/article/details/52235212题目&#xff1a;请实现一个函数&#xff0c;把字符串中的每个空格替换成“%20”。例如输入“We are happy.”&#xff0c;则输出“We%20are%20happy.”。解析&#xff1a;时间复杂度为O(n)的解法。完整代码及测试用例…

数据库原理及应用【一】引言

什么是数据库&#xff1a;一个大规模的集成的数据集合 作用&#xff1a;描述现实世界的实体(entities)以及实体之间的关系 管理数据库的系统软件&#xff1a;DBMS 文件是一个平滑的字符流&#xff0c;无法完成信息的检索和管理 数据&#xff08;data&#xff09;:用来描述现…

用c++模拟实现一个学生成绩管理系统

https://blog.csdn.net/yanxiaolx/article/details/53393437题目&#xff1a;用c模拟实现一个学生成绩的信息管理系统&#xff0c;要求能添加、删除、修改、查看和保存学生的信息等功能 源代码如下:[cpp] view plaincopy#define _CRT_SECURE_NO_WARNINGS #include<iostr…