博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
写时拷贝(Copy On Write)方案详解
阅读量:5030 次
发布时间:2019-06-12

本文共 9726 字,大约阅读时间需要 32 分钟。

本文旨在通过对 写时拷贝 的四个方案(Copy On Write)分析,让大家明白写时拷贝的实现及原理。

关于,我在之前的博客中已经阐述过了 

浅拷贝容易出现指针悬挂的问题,深拷贝效率低,但是我们可以应用引用计数来解决浅拷贝中多次析构的问题,写时拷贝也就应运而生了。

首先要清楚写时拷贝是利用浅拷贝来解决问题!!

方案一

class String{private:    char* _str;    int _refCount;};

方案一最不靠谱,它将用作计数的整形变量_refCount定义为类的私有成员变量,任何一个对象都有它自己的成员变量_refCount,它们互不影响,难以维护。只要拷贝出了对象,_refCount大于了0,每个对象在调用自己的析构函数时--_refCount不等于0,那么它们指向的那块内存都将得不到释放,无法达到我们要的效果。

spacer.gif

//以下是对方案一的简单实现,大家可以结合上图感受到方案一的缺陷 class String{public:    String(char* str = "")    //不能strlen(NULL)       :_refCount(0)    {       _str = new char[strlen( str) + 1];       strcpy(_str, str);       _refCount++;    }    String(String &s)       :_refCount( s._refCount)         {       _str = s._str;       _refCount++;       s._refCount = _refCount;               //这里虽然可以让两个对象的_refCount相等,       //但如果超过两个对象的_str指针都指向同一块内存时,       //就无法让所有对象的_refCount都保持一致       //这是方案一的缺陷之一    }    ~String()    {       if (--_refCount == 0)       {            delete[] _str;           _str = NULL;           cout << "~String " << endl;       }    }    friend ostream& operator<<( ostream& output, const String &s);private:    char* _str;    int _refCount;};ostream& operator<<( ostream& output, const String & s){    output << s._str;    return output;}void Test(){    String s1("aaa");    String s2(s1);    String s3(s2);    cout << s1 << endl;    cout << s2 << endl;    cout << s3 << endl;} 

方案二

class String{private:    char* _str;    static int count;};

设置了一个静态整形变量来计算指向一块内存的指针的数量,每析构一次减1,直到它等于0(也就是没有指针在指向它的时候)再去释放那块内存,看似可行,其实不然!

这个方案只适用于只调用一次构造函数、只有一块内存的情形,如果多次调用构造函数构造对象,新构造的对象照样会改变count的值,那么以前的内存无法释放会造成内存泄漏。

 

结合上图和下面的代码,我们可以清楚地看到该方案相比方案一的改善,以及缺陷

class String{public:    String(char* str = "")    //不能strlen(NULL)    {       _str = new char[strlen( str) + 1];       strcpy(_str, str);        count++;    }    String(const String &s)    {       _str = s._str;       count++;            }    String& operator=( String& s)      {       _str = s._str;       count++;       return *this;    }    ~String()    {       if (--count == 0)       {            delete[] _str;           _str = NULL;           cout << "~String " << endl;       }    }    friend ostream& operator<<( ostream& output, const String &s);    friend istream& operator>>( istream& input, const String &s);private:    char* _str;    static int count;};int String::count = 0;      //初始化count
void Test()    //用例测试{    String s1("abcdefg");    String s2(s1);    String s3;    s3 = s2;    cout << s1 << endl;    cout << s2 << endl;    cout << s3 << endl;     String s4("opqrst");    String s5(s4);    String s6 (s5);    s6 = s4;    cout << s4 << endl;    cout << s5 << endl;    cout << s6 << endl; }

方案三

问题的关键是,我们不是要为每一个对象建立一个引用计数,而是要每一块内存设置一个引用计数,只有这样才方便我们去维护。当指向这块内存的指针数为0时,再去释放它!

class String{    private:               char* _str;               int* _refCount;      };

方案三设置了一个int型的指针变量用来引用计数,每份内存空间对应一个引用计数,而不是每个对象对应一个引用计数,而且内存之间的引用计数互不影响,不会出现方案一和方案二出现的问题。

1.在实现赋值运算符重载时要谨慎,不要遇到下图的情形

 s1指向内存1,s2指向内存2,利用s2拷贝出的对象s3也指向内存块2,这时候内存块1的引用计数等于1 ,内存块2的引用计数等于2。一切似乎都很正常,但是调用赋值运算符重载执行语句:s2=s1后,错误慢慢显现出来了。将s2指向内存1 并把内存1 的引用计数加1,这理所当然,但是不能把s2原本指向的空间直接delete,s3还指向内存2着呢!这里千万在释放一块空间前,对指向这块内存的引用计数进行检查,当引用计数为0的时候再去释放,否则只做减引用计数就行。

//错误代码 String& operator=(String& s)      {        if (_str!= s._str)        {            delete[] _str;            delete _refCount;                      _str = s._str;            _refCount = s._refCount;            (*_refCount)++;        }              return *this;    } 

2.改变字符串的某个字符时要谨慎,不要遇到类似下图所遇到的问题。

如果多个对象都指向同一块内存,那么只要一个对象改变了这块内存的内容,那所有的对象都被改变了!!

如下图:当s1和s2都指向内存块1,s3经过赋值运算符重载后也指向内存块1,现在s2如果对字符串进行修改后,所有指向内存块1 的指针指向的内容都会被改变!

可以用下图的形式改善这种问题:新设置一块内存来存要改变的对象,这样就不会影响其他的对象了

案例3我画的图较多,方便大家结合代码去理解 

//案例三 class String{public:    String(char* str = "")    //不能strlen(NULL)    {        _refCount = new int(1);     //给_refCount开辟空间,并赋初值1        _size = strlen(str);        _capacity = _size + 1;        _str = new char[strlen(str) + 1];        strcpy(_str, str);    }    String(const String &s)    {        _refCount = s._refCount;        _str = s._str;        _size = strlen(s._str);        _capacity = _size + 1;        (*_refCount)++;      //拷贝一次_refCount都要加1             }         //要考虑是s1=s2时,s1原先不为空的情况,要先释放原内存    //如果要释放原内存时,要考虑它的_refCount减1后是否为0,为零再释放,否则其它对象指针无法再访问这片空间    String& operator=(String& s)      {        if (_str!= s._str)        {            _size = strlen(s._str);            _capacity = _size + 1;            if (--(*_refCount) == 0)            {                delete[] _str;                delete _refCount;            }                         _str = s._str;            _refCount = s._refCount;            (*_refCount)++;        }              return *this;    }     //如果修改了字符串的内容,那所有指向这块内存的对象指针的内容间接被改变    //如果还有其它指针指向这块内存,我们可以从堆上重新开辟一块内存空间,    //把原字符串拷贝过来    //再去改变它的内容,就不会产生链式反应    //  1.减引用计数  2.拷贝   3.创建新的引用计数    char& String::operator[](const size_t index) //参考深拷贝          {        if (*_refCount==1)        {            return *(_str + index);        }        else        {            --(*_refCount);            char* tmp = new char[strlen(_str)+1];            strcpy(tmp, _str);            _str = tmp;            _refCount = new int(1);            return *(_str+index);        }    }    ~String()    {        if (--(*_refCount)== 0)  //当_refCount=0的时候就释放内存        {            delete[] _str;            delete _refCount;            _str = NULL;            cout << "~String " << endl;        }        _size = 0;        _capacity = 0;    }    friend ostream& operator<<(ostream& output, const String &s);    friend istream& operator>>(istream& input, const String &s);private:    char* _str;      //指向字符串的指针    size_t  _size;      //字符串大小    size_t  _capacity;   //容量    int* _refCount;    //计数指针};  ostream& operator<<(ostream& output, const String &s){    output << s._str;    return output;}istream& operator>>(istream& input, const String &s){    input >> s._str;    return input;} void Test()    //用例测试{    String s1("abcdefg");    String s2(s1);    String s3;    s3 = s2;    cout << s1 << endl;    cout << s2 << endl;    cout << s3 << endl;    s2[3] = '0';    cout << s1 << endl;    cout << s2 << endl;    cout << s3 << endl;} 

方案四

class String{   private:           char* _str;};

 

方案四与方案三类似。方案四把用来计数的整型指针变量放在所开辟的内存空间的首部。

用*((int*)_str)就能取得计数值

class String{public:           String(char * str = "" )    //不能strlen(NULL)           {                    _str = new char[strlen( str) + 5];                    _str += 4;                    strcpy(_str, str);                    GetRefCount(_str) = 1;           }           String(const String &s)           {                    _str = s._str;                    ++GetRefCount(_str);           }            //要考虑是s1=s2时,s1原先不为空的情况,要先释放原内存           //如果要释放原内存时,要考虑它的_refCount减1后是否为0,           //为零再释放,否则其它对象指针无法再访问这片空间           String& operator=(String& s)           {                    if (this != &s )                    {                              if (GetRefCount(_str ) == 1)                              {                                       delete (_str-4);                                       _str = s._str;                                       ++GetRefCount(_str );                              }                              else                              {                                       --GetRefCount(_str );                                       _str = s._str;                                       ++GetRefCount(_str );                              }                    }                    return *this ;           }           //如果修改了字符串的内容,那所有指向这块内存的对象指针的内容间接被改变           //如果还有其它指针指向这块内存,我们可以从堆上重新开辟一块内存空间,           //把原字符串拷贝过来.           //再去改变它的内容,就不会产生链式反应                                 char& String ::operator[](const size_t index ) //深拷贝                {                                                  if (GetRefCount(_str) == 1)                              {                                       return _str[index ];                              }                              else                              {                                        //  1.减引用计数                                       --GetRefCount(_str );                                        //  2.拷贝     3.创建新的引用计数                                       char* tmp = new char [strlen(_str) + 5];                                         *((int *)tmp) = 1;                                       tmp += 4;                                       strcpy(tmp, _str);                                       _str = tmp;                                       return _str[index ];                              }           }            int& GetRefCount(char* ptr)    //获取引用计数(隐式内联函数)           {                    return *((int *)(ptr -4));           }           ~String()           {                    if (--GetRefCount(_str) == 0)                    {                              cout << "~String" << endl;                              delete[] (_str-4);                                 }                      }           friend ostream& operator<<( ostream& output, const String &s);           friend istream& operator>>( istream& input, const String &s);private:           char* _str; };  ostream& operator<<(ostream& output, const String &s){           output << s._str;           return output;}istream& operator>>(istream& input, const String &s){           input >> s._str;           return input;} void Test()  //用例测试{           String s1("abcdefg" );           String s2(s1);           String s3;           s3 = s2;           cout << s1 << endl;           cout << s2 << endl;           cout << s3 << endl;           s2[3] = '0';           cout << s1 << endl;           cout << s2 << endl;           cout << s3 << endl;} 

转载于:https://www.cnblogs.com/Lynn-Zhang/p/5400714.html

你可能感兴趣的文章
c# aop讲解
查看>>
iterable与iterator
查看>>
返回顶部(动画)
查看>>
webpack+react+antd 单页面应用实例
查看>>
Confluence 6 SQL Server 数据库驱动修改
查看>>
Confluence 6 通过 SSL 或 HTTPS 运行 - 备注和问题解决
查看>>
【47.76%】【Round #380B】Spotlights
查看>>
Git(使用码云)
查看>>
分享Java web 开发必游之路
查看>>
IIS初始化(预加载),解决第一次访问慢,程序池被回收问题(转载)
查看>>
Bean的Scope
查看>>
【BZOJ】3142: [Hnoi2013]数列
查看>>
http初探
查看>>
W3C标准以及规范
查看>>
elasticsearch的安装
查看>>
__next__()
查看>>
爬取:中国大学排名
查看>>
聊天室(C++客户端+Pyhton服务器)_1.框架搭设
查看>>
UpdatePanel 内控件 更新“外的”控件【转】
查看>>
[CF508E] Arthur and Brackets
查看>>