c++写时拷贝1

http://blog.csdn.net/SuLiJuan66/article/details/48882303

Copy On Write

Copy On Write(写时复制)使用了“引用计数”(reference counting),会有一个变量用于保存引用的数量。当第一个类构造时,string的构造函数会根据传入的参数从堆上分配内存,当有其它类需要这块内存时,这个计数为自动累加,当有类析构时,这个计数会减一,直到最后一个类析构时,此时的引用计数为1或是0。此时,程序才会真正的Free这块从堆上分配的内存。

写时复制(Copy-On-Write)技术,就是编程界“懒惰行为”——拖延战术的产物。举个例子,比如我们有个程序要写文件,不断地根据网络传来的数据写,如果每一次fwrite或是fprintf都要进行一个磁盘的I/O操 作的话,都简直就是性能上巨大的损失,因此通常的做法是,每次写文件操作都写在特定大小的一块内存中(磁盘缓存),只有当我们关闭文件时,才写到磁盘上 (这就是为什么如果文件不关闭,所写的东西会丢失的原因)。

class String
{
public:
    String(char* ptr = "")           //构造函数
        :_ptr(new char[strlen(ptr)+1])
    {
        strcpy(_ptr, ptr);
    }
    String(const String& s)
        :_ptr(new char[strlen(s._ptr)+1])//另外开辟空间
    {
        strcpy(_ptr, s._ptr);
    }
    ~String()
    {
        if (_ptr)
        {
            delete[] _ptr;
        }
    }
private:
    char* _ptr;
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
void Test()
{
    String s1 = "hello world";
    int begin = GetTickCount();//记录此时毫秒数
    for (int i = 0; i < 10000; ++i)
    {
        String s2 = s1;
    }
    int end = GetTickCount();//记录此时毫秒数
    cout << "cost time:" << end - begin << endl;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

  • GetTickCount : 在Release版本中,该函数从0开始计时,返回自设备启动后的毫秒数(不含系统暂停时间)。在头文件windows.h中。

  • 在上面for循环中,语句“String s2 = s1;”不断调用拷贝构造函数为s2开辟空间,执行完语句“String s2 = s1;”后,不断调用析构函数对s2进行释放,导致低效率,Test执行结果如下图:

    这里写图片描述



  • 写时拷贝~~写时拷贝~自然是我们自己想写的时候再进行拷贝(复制),下面引入几种方案如下:(试着判断哪一种方案可行)

这里写图片描述



  • 这里又引入另外一个概念“引用计数”:string的构造函数会根据传入的参数从堆上分配内存,当有其它类需要这块内存时(即其它对象也指向这块内存),这个计数为自动累加,上面方案中的_retCount就是用来计数的。

  • 简单地介绍一下上面三个方案。方案一和方案二是不可行的,方案一中的_retCount是属于每个对象内部的成员,当有多个对象同时指向同一块空间时,_retCount无法记录多个对象方案二中的_retCount是静态成员变量,是所有对象所共有,似乎可以记录,举个例子:对象s1、s2指向A空间,_retCount为2,对象s3、s4指向B空间,此时_retCount变为4,但是当想释放B空间时,应当在析构函数中_retCount减到0时释放,但是当_retCount减到0时,却发现释放的是A空间,而B空间发生了内存泄露。也就是静态成员变量_retCount只能记录一块空间的对象个数。

- 下面通过代码介绍方案三:

class String
{
public:
    String(char* ptr = "")        //构造函数
        :_ptr(new char[strlen(ptr)+1])
        , _retCount(new int(1))//每个对象对应一个整型空间存放
    {                          //指向这块空间的对象个数
        strcpy(_ptr, ptr);
    }
    String(const String& s)       //拷贝构造函数
        :_ptr(s._ptr)
        , _retCount(s._retCount)
    {
        _retCount[0]++;
    }
    String& operator= (const String& s)   //赋值运算符重载
    {
        if (this != &s)
        {
            if (--_retCount[0] == 0)
            {//旧的引用计数减1,如果是最后一个引用对象,则释放对象
                delete[] _ptr;
                delete[] _retCount;
            }
            _ptr = s._ptr;//改变this的指向,并增加引用计数
            _retCount = s._retCount;
            ++_retCount[0];
        }
        return *this;
    }
    ~String()
    {
        if (--_retCount[0] == 0)
        {
            delete[] _ptr;
            delete[] _retCount;
        }
    }
private:
    char* _ptr;
    int* _retCount;
};
 
 
  • 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

  • 同样执行Test函数,测试结果如下图:

这里写图片描述



下面进一步优化方案三来介绍写时拷贝(写时复制)


方案三:是每个对象对应一个整型空间(即_refCount)存放指向这块空间的对象个数
再优化:不引用_refCount,但每次给_ptr开辟空间的时候,多开辟四个字节,用来记录指向此空间的对象个数,规定用开头那四个字节来计数。
class String
{
public:
    String(char* ptr = "")
        :_ptr(new char[strlen(ptr)+5])
    {
        _ptr += 4;
        strcpy(_ptr,ptr);
        _GetRefCount(_ptr) = 1;//每构造一个对象,头四个字节存放计数
    }
    String(const String& s)
        :_ptr(s._ptr)
    {
        _GetRefCount(_ptr)++;  //每增加一个对象,引用计数加1
    }
    String& operator= (const String& s)
    {
        if (this != &s)
        {
            Release(_ptr);
            _ptr = s._ptr;
            _GetRefCount(_ptr)++;
        }
        return *this;
    }
    char& operator [](size_t index)
    {
        if (_GetRefCount(_ptr) > 1)
        {
            --_GetRefCount(_ptr);//旧引用计数减1
            char* str = new char[strlen(_ptr) + 1];//另外开辟一个空间
            str += 4;
            strcpy(str, _ptr);
            _GetRefCount(str) = 1;
            _ptr = str;
        }
    }
    ~String()
    {
        Release(_ptr);
    }
    inline void Release(char* ptr)
    {
        if (--_GetRefCount(ptr) == 0)
        {
            delete[](ptr - 4);
        }
    }
    inline int& _GetRefCount(char* ptr)
    {
        return *(int*)(ptr - 4);//访问头四个字节
    }
private:
    char* _ptr;
};
 
 
  • 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

程序执行过程,看下图说话



这里写图片描述



这里写图片描述


对下列函数进行解析:
char& operator [](size_t index)
    {
        if (_GetRefCount(_ptr) > 1)
        {
            --_GetRefCount(_ptr);//旧引用计数减1
            char* str = new char[strlen(_ptr) + 1];//另外开辟一个空间
            str += 4;
            strcpy(str, _ptr);
            _GetRefCount(str) = 1;
            _ptr = str;
        }
    }
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
当在主函数中执行语句:s1[0] = ‘w’;时,想要改变s1对象中_ptr[0]的值;但是当我们改变s1中_ptr[0]的值时,不希望把s2、s3中_ptr[0]的值也改变了。由于s1、s2、s3目前指向同一块空间,改变其中一个,另外两个肯定也跟着改变了,所以提供了另外一种方法:把对象s1分离出来,旧引用计数减1,另外给s1开辟一段跟原来一样的空间,存放一样的内容,这时候即使改变了s1的内容,也不影响s2、s3的对容。

一样看下图说话:

这里写图片描述


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值