C++:写时拷贝

一、浅拷贝
一个类,如果不写拷贝构造函数,那么它的默认拷贝构造函数是浅拷贝,浅拷贝有什么问题呢?
用一个string来举例:

class String
{
public:
	String(char* str = "\0")
		:_str(new char[strlen(str)+1])
	{
		strcpy(_str, str);
	}

	~String()
	{
		if(_str)
		{
			delete[] _str;
		}
	}
private:
	char* _str;
};
int main()
{
	String s1("hello String");
	String s2(s1);
	return 0;
}

在main函数中,创建了s1对象,然后通过s2对象是通过s1拷贝构造而来,由于没有写拷贝构造函数,所以编译器会自动生成成,但编译器自动生成的是浅拷贝,所以程序会崩溃。
在这里插入图片描述
因为是浅拷贝,所以两个对象的_str指针同时指向了一块空间,然后这两个对象的生命周期结束时,都会调用析构函数,那么这块空间就会double free,也就是被析构了两次;
还有一个问题,如果改变s1对象的指针指向的空间,由于两个对象的指针指向同一块空间,所以s2对象的指针所指向的空间也一起变了,这是不应该的,两对象应该是各自的。
所以总结一下:浅拷贝的问题:1、析构多次。 2、一个改变会影响另外一个。

二、深拷贝
什么是深拷贝呢,就是我在拷贝你的时候,重新开辟一块空间,然后把你的数据复制到我的空间里,这样,咱两各是各的,就不会相互产生影响了。
在这里插入图片描述
代码实现:

String(const String& s)
    :_str(new char[strlen(s._str)+1])
{
    strcpy(_str, s._str);
}

但是深拷贝要重新开空间,复制数据,代价太大,如果我们既可以做到浅拷贝,有能解决析构时一块空间被多次释放的问题,这个就需要引用计数。

三、引用计数

private:
	char* _str;
	int* _refCount;

_refCount指向的空间是专门用来存放_str指向的空间同时被多少对象所指向

1、构造对象时,new出来的空间只被_str指向,所以引用计数是1
在这里插入图片描述

String(char* str = "\0")
		:_str(new char[strlen(str)+1])
		,_refCount(new int(1))
	{
		strcpy(_str, str);
	}

2、拷贝构造时,多出来一个对象的指针指向_str,所以引用计数要+1
在这里插入图片描述

String(String& s)
	{
		this->_str = s._str;
		_refCount = s._refCount;
		++(*this->_refCount);
	}

3、operator同样是需要拷贝,所以多出来一个对象指向_str,引用计数+1
和拷贝构造不一样,operator=是把一个已经构造好的对象重新赋值,而拷贝构造是构造一个新对象,所以operator之前,我们需要考虑之前构造好的对象的引用计数是多少。
如果之前的对象引用计数为1,我们又给它重新赋值,所以之前的对象的指针指向的空间就应该被释放了。
如果之前的对象引用计数大于1,就算重新赋值了,也还有其它对象的指针指向这块空间,所以不需要释放。
在这里插入图片描述

String& operator=(const String& s)
	{
		if(this != &s)
		{
			if((*refCount) == 1)
			{
				delete[] _str;
				delete[] _refCount;
			}
			_str = s._str;
			_refCount = s._refCount;
			(*_refCount)++;
		}
		return *this;
	}

使用引用计数,重要的是重写析构函数,每一次析构都要给引用计数减1,只有当引用计数为1时,才释放空间。

~String()
	{
		if(--(*refCount) == 0)
		{
			delete[] this->_str;
			delete[] this->_refCount;
		}
	}

现在呢,我们已经通过引用计数解决了浅拷贝的析构多次会崩溃的问题,但还是没有解决改变一个会对另外一个产生影响的问题。

什么是写时拷贝呢?,简单的说就是写的时候才拷贝,也就是说,你如果要改变,就需要拷贝一份,改变的是拷贝的这一份。

那什么场景会改变String类的数据呢?

char& operator[](size_t pos)
	{
		return _str[pos];
	}

operator[]会返回引用,这样你就可以通过我的返回值来改变我的_str指向的数据。

写时拷贝的实现:


	void CopyOnWrite()
	{
		//如果引用计数为1,直接写不影响
		if(*refCount > 1)
		{
			char* tmp = new char[strlen(_str)+1];
			strcpy(tmp, _str);
			(*refCount)--;
			_str = tmp;
		}
	}

接下来,只需要把所有可能被用户用来改变数据的接口,先写时拷贝一份就好了。
例如:

char& operator[](size_t pos)
	{
		CopyOnWrite();
		return _str[pos];
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值