Welcome 微信登录
编程资源 图片资源库 蚂蚁家优选 PDF转换器

首页 / 操作系统 / Linux / 由浅入深地分析 写时拷贝(Copy On Write)

本文旨在通过对 写时拷贝 的四个方案(Copy On Write)分析,让大家明白写时拷贝的实现及原理。深拷贝效率低,我们可以应引用计数的方式去解决浅拷贝中析构多次的问题。首先要清楚写时拷贝是利用浅拷贝来解决问题!!方案一class String
{
private:
    char* _str;
    int _refCount;
};方案一最不靠谱,它将用作计数的整形变量_refCount定义为类的私有成员变量,任何一个对象都有它自己的成员变量_refCount,它们互不影响,只要拷贝出了对象,_refCount大于了1,那么每个对象调用自己的析构函数时--_refCount不等于0,那么它们指向的那块内存都将得不到释放,无法达到我们要的效果。//以下是对方案一的简单实现,大家可以结合上图感受到方案一的缺陷
 
#define _CRT_SECURE_NO_WARNINGS 1
 
#include<iostream>
using namespace std;
#include<assert.h>
 
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;
}
int main()
{
    Test();
    system("pause");
    return 0;
}方案二class String
{
private:
    char* _str;
    static int count;
};设置一个静态整形变量来计算指向一块内存的指针的数量,每析构一次减1,直到它等于0(也就是没有指针在指向它的时候)再去释放那块内存,看似可行,其实不然!这个方案只适用于只调用一次构造函数、只有一块内存的情形,如果多次调用构造函数构造对象,新构造的对象照样会改变count的值,那么以前的内存无法释放会造成内存泄漏。结合上图和下面的代码,我们可以清楚地看到该方案相比方案一的改善,以及缺陷#define_CRT_SECURE_NO_WARNINGS 1
 
 
#include<iostream>
using namespace std;
#include<assert.h>
 
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;
};
ostream& operator<<( ostream& output, const String & s)
{
    output << s._str;
    return output;
}
istream& operator>>( istream& input, const String & s)
{
    input >> s._str;
    return input;
}
 
int String::count = 0;      //初始化count
 
void Test()
{
    String s1("aaa");
    String s2(s1);
    String s3 = s2;
    cout << s1 << endl;
    cout << s2 << endl;
    cout << s3 << endl;
 
}
int main()
{
    Test();
    system("pause");
    return 0;
}方案三class String
{
    private:
             char* _str;
             int* _refCount;     
};方案三设置了一个int型的指针变量用来引用计数,每份内存空间对应一个引用计数,而不是每个对象对应一个引用计数,而且每块内存的引用计数互不影响,不会出现方案一和方案二出现的问题。1.在实现赋值运算符重载要谨慎,不要遇到下图的情形2.改变字符串的某个字符时要谨慎,不要遇到类似下图所遇到的问题。如果多个对象都指向同一块内存,那么只要一个对象改变了这块内存的内容,那所有的对象都被改变了!!可以用下图的形式改善这种问题:新设置一块内存来存要改变的对象案例3我画的图较多,方便大家结合代码去理解#define _CRT_SECURE_NO_WARNINGS 1
 
#include<iostream>
using namespace std;
#include<assert.h>
 
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;
 
    //String s4("opqrst");
    //String s5(s4);
    //String s6 (s5);
    //s6 = s4;
    //cout << s4 << endl;
    //cout << s5 << endl;
    //cout << s6 << endl;
 
}
int main()
{
    Test();
    system("pause");
    return 0;
}方案四class String{ private:         char* _str;};方案四与方案三类似。方案四把用来计数的整形变量放在所开辟的内存空间的首部,用*((int*)_str)就能取得计数值#define_CRT_SECURE_NO_WARNINGS 1
 
#include<iostream>
using namespace std;
#include<assert.h>
 
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;
 
         //String s4("opqrst");
         //String s5(s4);
         //String s6 (s5);
         //s6 = s4;
         //cout << s4 << endl;
         //cout << s5 << endl;
         //cout << s6 << endl;
 
}
int main()
{
         Test();
         system("pause" );
         return 0;
}本文永久更新链接地址:http://www.linuxidc.com/Linux/2016-05/131825.htm