小结 | C++ String类的引用计数的浅拷贝、写时拷贝

来源:互联网 发布:电音和摇滚知乎 编辑:程序博客网 时间:2024/05/19 19:30

这篇博客将一步一步写出写实拷贝的的思路,以模拟实现string类为例子

1、深浅拷贝

#include <iostream>#include <stdlib.h>class String{public:    String(const char* str = "")        :_str(new char[strlen(str)+1])    {        strcpy(_str, str);    }    //浅拷贝    String(const String&s)    {        _str = s._str;    }    ////深拷贝    //String(const String&s)    //  :_str(new char[strlen(s._str)+1])    //{    //  strcpy(_str, s._str);    //}    ~String()    {        if (_str)        {            delete[] _str;        }    }private:    char*_str;};int main(){    String s1("123455");    String s2(s1);    return 0;}

上面的程序使用浅拷贝将会崩溃。因为浅拷贝是直接指向了同一块空间,在调用析构函数的时候会对同一块空间delete[]了两次。这样是不合法的。使用深拷贝将会没有任何问题。
这里写图片描述

使用了深拷贝,其实就是另外开辟一块空间,再将原来空间的值拷贝下来。
这里写图片描述

2、引用计数的浅拷贝

如果我们不想用深拷贝,因为深拷贝会开辟空间,这样浪费空间,同时还影响效率。那么我们就可以使用引用计数的浅拷贝。引用计数的浅拷贝指的是当一个类还有对象存在的时候,就不进行析构。这样就避免对同一块空间的多次delete[]。

(1)引入static _refcount当做计数器

class String{public:    String(const char* str = "")        :_str(new char[strlen(str)+1])    {        strcpy(_str, str);    }    //浅拷贝    String(const String&s)    {        _str = s._str;        _refcount++;//进行拷贝构造,增加引用计数的个数    }    ~String()    {        if (--_refcount == 0)        {            delete[] _str;        }    }private:    char*_str;    static int _refcount;//定义为静态的};int String::_refcount = 1;//静态全局变量需要在类外面初始化

定义为静态的_refcount 是为了让对象共用一个。而不是每一个对象都有一个引用计数。
这样似乎是对的,但是一个类可以同时开辟出多个对象,这多个对象都有自己的空间,但是使用了静态的_refcount的话,他们都共有一个引用计数,这样依然会对同一块空间有多次的delete[]。

这里写图片描述

(2)使用指针类型的_refcount

为了克服以上的问题,我们打算开辟一个整型大小空间,用int* ——refcount维护这块空间。这样就可以保证每一块空间搭配一个引用计数,同时同一个对象拷贝构造出来的使用的是同一个引用计数。

class String{public:    String(const char* str = "")        :_str(new char[strlen(str) + 1])        , _refcount(new int(1))    {        strcpy(_str, str);    }    String(const String& s)        :_str(s._str)    {        _refcount = s._refcount;        (*_refcount)++;    }    //s1 = s2    String& operator=(const String& s)    {        if (_str != s._str)        {            if (--(*_refcount) == 0)            {                delete[] _str;                delete _refcount;                _str = NULL;                _refcount = NULL;            }            _str = s._str;            _refcount = s._refcount;            ++(*_refcount);        }        return *this;    }    ~String()    {        if (--(*_refcount) == 0)        {            delete[] _str;            delete _refcount;            _str = NULL;            _refcount = NULL;        }    }private:    char* _str;    int* _refcount;};int main(){    String s1("123455");    String s2(s1);    String s3(s2);    //创建新的空间    String s4("0009");    String s5(s4);    return 0;}      

s1\s2\s3指向同一块空间,共用一个引用计数。s4\s5指向另外一块空间,共用另一个引用计数
这里写图片描述

这里写图片描述

(3)将引用计数放到开辟空间的前面

使用指针的引用计数是一种好的方式,但是这样会增加内存空间的碎片化。为了解决内存碎片化,我们可以在开辟的_str 空间的前面多开一个int大小的空间,用作存放引用计数。模仿new[]的做法。

修改代码如下:

class String{public:    String(const char* str = "")        //strlrn(str) + 1是给'\0'留下的空间,+4是给引用计数        :_str(new char[strlen(str) + 1 + 4])    {        *((int*)_str) = 1;        //让_str指向引用计数之后的空间,即字符串的开始        _str += 4;        strcpy(_str, str);    }    String(const String& s)        //让_str指向引用计数        :_str(s._str-4)    {        //引用计数加一        (*((int*)_str))++;        _str += 4;    }    //s1 = s2    String& operator=(const String& s)    {        if (_str != s._str)        {            //引用计数减一判0            if (--(*((int*)_str-1)) == 0)            {                delete[] _str;                _str = NULL;            }            _str = s._str;            //引用计数加一            (*((int*)s._str - 1))++;        }        return *this;    }    ~String()    {        if (--(*((int*)_str - 1)) == 0)        {            delete[] _str;            _str = NULL;        }    }private:    char* _str;};int main(){    String s1("123455");    String s2(s1);    String s3(s2);    String s4("0009");    String s5(s4);    return 0;}      

这里写图片描述

这里写图片描述

至此,我们的引用计数的浅拷贝已经完成的很好了。

3、写时拷贝

有一个问题,因为s1、s2同时指向了同一块空间,如果我对s1进行了值的修改,必定会同时修改了s2的值。我们不希望这事情的发生,因此我们引入了写时拷贝。写时拷贝的意思是,当我需要向空间写入值的时候,即修改值的时候,我们就将修改的对象拷贝一份出来。这样就不会影响到其他的对象了。

void CopyOnWrite(){    if (*((int*)_str-1) > 1)    {        (*((int*)_str - 1))--;        char* tmp = new char[strlen(_str) + 5];        tmp -= 4;        strcpy(tmp, _str);        _str = tmp;        (*((int*)_str+1))++;    }}

这里写图片描述

原创粉丝点击