写时拷贝 Linux,由浅入深地分析 写时拷贝(Copy On Write)

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

深拷贝效率低,我们可以应引用计数的方式去解决浅拷贝中析构多次的问题。

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

方案一

class String

{

private:

char* _str;

int _refCount;

};

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

bc118eb9270f1ce7abf8f1a340cdee08.png

//以下是对方案一的简单实现,大家可以结合上图感受到方案一的缺陷

#define _CRT_SECURE_NO_WARNINGS 1

#include

using namespace std;

#include

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<

private:

char* _str;

int _refCount;

};

ostream& operator<

{

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的值,那么以前的内存无法释放会造成内存泄漏。

f096ac889bd946e9826579e2d0267af5.png

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

#define_CRT_SECURE_NO_WARNINGS 1

#include

using namespace std;

#include

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<

friend istream& operator>>( istream& input, const String &s);

private:

char* _str;

static int count;

};

ostream& operator<

{

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型的指针变量用来引用计数,每份内存空间对应一个引用计数,而不是每个对象对应一个引用计数,而且每块内存的引用计数互不影响,不会出现方案一和方案二出现的问题。

9b5c8ffc13715c2ed48563d4e6d81365.png

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

b3d5909b15ff86a0dd78c9e2a20b11bb.png

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

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

f31929809766eb6946a65b9978abe875.png

可以用下图的形式改善这种问题:新设置一块内存来存要改变的对象

c28efe3851e69dcf501a0b48e1e6dc71.png

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

#define _CRT_SECURE_NO_WARNINGS 1

#include

using namespace std;

#include

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<

friend istream& operator>>(istream& input, const String &s);

private:

char* _str;      //指向字符串的指针

size_t  _size;      //字符串大小

size_t  _capacity;  //容量

int* _refCount;    //计数指针

};

ostream& operator<

{

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)就能取得计数值

4d2d29022e5b8a52f2eadbfad8c366eb.png

#define_CRT_SECURE_NO_WARNINGS 1

#include

using namespace std;

#include

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<

friend istream& operator>>( istream& input, const String &s);

private:

char* _str;

};

ostream& operator<

{

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;

}

0b1331709591d260c1c78e86d0c51c18.png

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值