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…

C++函数默认参数

声明是用户可以看到的部分&#xff0c;客户非常信任地使用这个特性&#xff0c;希望得到一定的结果&#xff0c;但是你在实现里使用了不同的缺省值&#xff0c;那么将是灾难性的。因此编译器禁止声明和定义时同时定义缺省参数值。 类的成员函数的参数表在声明时默认参数位于参…

C语言链表各类操作详解

http://blog.csdn.net/pf4919501/article/details/38818335链表概述   链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。它可以根据需要开辟内存单元。链表有一个“头指针”变量&#xff0c;以head表示&#xff0c;它存放一个地址。该地址指向一个元素。…

Java笔试复习

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

数据结构之自建算法库——链栈

http://blog.csdn.net/sxhelijian/article/details/48463801本文针对数据结构基础系列网络课程(3)&#xff1a;栈和队列中第4课时栈的链式存储结构及其基本运算实现。 按照“0207将算法变程序”[视频]部分建议的方法&#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实现高并发聊天室

http://blog.csdn.net/qq_31564375/article/details/51581038项目介绍 本项目是实现一个简单的聊天室&#xff0c;聊天室分为服务端和客户端。本项目将很多复杂的功能都去掉了&#xff0c;线程池、多线程编程、超时重传、确认收包等等都不会涉及。总共300多行代码&#xff0c;让…

BZOJ2809-左偏树合并

Description 在一个忍者的帮派里&#xff0c;一些忍者们被选中派遣给顾客&#xff0c;然后依据自己的工作获取报偿。在这个帮派里&#xff0c;有一名忍者被称之为 Master。除了 Master以外&#xff0c;每名忍者都有且仅有一个上级。为保密&#xff0c;同时增强忍者们的领导力&a…

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

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

epoll详解

http://blog.csdn.net/majianfei1023/article/details/45772269欢迎转载&#xff0c;转载请注明原文地址&#xff1a;http://blog.csdn.net/majianfei1023/article/details/45772269一.基本概念&#xff1a;1.epoll是什么&#xff1a;epoll是Linux内核为处理大批量文件描述符而…

数据分割-并查集+set

小w来到百度之星的赛场上&#xff0c;准备开始实现一个程序自动分析系统。 这个程序接受一些形如xixj 或 xi≠xj 的相等/不等约束条件作为输入&#xff0c;判定是否可以通过给每个 w 赋适当的值&#xff0c;来满足这些条件。 输入包含多组数据。 然而粗心的小w不幸地把每组数据…

linux c++线程池的实现

http://blog.csdn.net/zhoubl668/article/details/8927090?t1473221020107 线程池的原理大家都知道&#xff0c;直接上代码了^_^ Thread.h [cpp] view plaincopy #ifndef __THREAD_H #define __THREAD_H #include <vector> #include <string> #inc…

树启发式合并入门

所谓启发式合并&#xff0c;就是一种符合直觉的合并方法&#xff1a;将小的子树合并在大的子树上。 这些问题一般是相似的问题背景&#xff1a;都是树上的计数问题&#xff0c;都不能直接从上往下进行暴力&#xff0c;都需要从下往上计数时对子树信息进行运算从而得到父亲节点的…

链栈基本操作

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;而不是阻…

UVa12633-Super Rooks on Chessboard-容斥+FFT

题目大意就是给你一个R*C的棋盘&#xff0c;上面有超级兵&#xff0c;这种超级兵会攻击 同一行、同一列、同一主对角线的所有元素&#xff0c;现在给你N个超级兵的坐标&#xff0c;需要你求出有多少方块是不能被攻击到的(R,C,N<50000) 遇到这种计数问题就要联想到容斥&#…

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…

FFT模板

整理了一下&#xff0c;自己写了一下模板 const double PIacos(-1.0); struct complex {double r,i;complex(double _r0,double _i0):r(_r),i(_i){}complex operator (const complex &b) {return complex(rb.r,ib.i);}complex operator -(const complex &b) {return c…

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;依靠树和子树之间的关系进行分治从而降低复杂度。 和其他树上的算法有一些区别…