C++写时拷贝的不同方案(String类)

String类的写时拷贝,不同方案分析。

在使用String类时使用浅拷贝会产生一块空间析构多次的问题,所以产生了深拷贝,每次对对象进行值和空间同时拷贝,但这样会使用更多的空间。为了避免产生更多的空间,引入写时拷贝,当对空间进行更改时,检查是否有除自己外别的对象使用这块空间,若有,则自己重新开辟空间进行更改,不影响其他对象;若没有其他对象使用此空间,则说明只有自己使用此空间,就进行更改。此时引入引用计数,用于统计有多少对象在使用此空间。

方案1:

如下图所示:

当创建一个对象s1,再拷贝构造一个对象s2时,引用计数_refCount自动加1,此时为2。若此时s2要对自身进行修改,则s2重新开辟一块空间,检查引用计数_refCount是否大于1,若大于1,,则进行修改,这样就不会影响其他对象。

s2重新开辟一块儿空间,再修改s1原来空间的引用计数_refCount,使其减1,重新开辟的空间中因为此时只有s2一个对象,所以引用计数_refCount也为1.

小结:此方案的写时拷贝计数是同时开辟两块空间,一个自身存放的内容,一个存放引用计数_refCount,同时管理两块空间,统计当前使用此空间的对象数,当要修改当前空间的时候,进行对引用计数的判断,再决定是否开辟新的空间。

代码如下:

class String
{
public:
	//构造函数
	String(char* str="")
		:_str(new char[strlen(str)+1])
		,_refCount(new int(1))
	{
		strcpy(_str, str);
	}


	//拷贝构造函数
	String(String& s)
	{
		_str = s._str;
		_refCount = s._refCount;
		++(*_refCount);
	}


	String& operator=(const String& s)
	{
		if (_str != s._str)
		{
			Release();

			_str = s._str;
			_refCount = s._refCount;
			(*_refCount)++;
		}
		return *this;
	}


	void Release()
	{
		if (--(*_refCount) == 0)
		{
			cout << "delete" << endl;
			delete[] _str;
			delete _refCount;
		}
	}

	~String()
	{
		Release();
	}

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

			_str = tmp;
			_refCount = new int(1);
		}
	}

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

	char operator[](size_t pos) const
	{
		return _str[pos];
	}
private:
	char* _str;
	int* _refCount;
};


方案2:

方案1中是开辟了两块空间进行管理,方案2采用开辟一块空间进行写时拷贝的操作。

开辟一块空间,在这块空间的头4个字节中放置引用计数,真正存放内容的空间从第5个字节开始。一次性多开辟4个字节进行写时拷贝的操作。

具体如下图所示:

当进行操作时,先检查引用计数的个数,然后进行判断是否开辟新的空间,同时修改引用计数的值,防止空间不能释放。

具体例子如下:
当创建了3个对象,s1,s2,s3同时指向一个空间,此时引用计数为3,再创建1个对象s4,s4的引用计数为1。


再进行操作s3 = s4;此时对应的引用计数和对象的指向都需要更改,更改之后如下图所示:

对象s3指向了s4,同时s3原来的空间的引用计数进行减1,新指向空间的引用计数进行加1.


小结:方案2的写时拷贝计数使用一块空间进行内容和引用计数的管理和操作,不开辟两块空间,方便管理。

代码如下:

class String
{
public:
	String(const char* str)
		:_str(new char[strlen(str) + 5])
	{
		_str += 4;
		strcpy(_str, str);
		GetRefCount();
	}


	String(const String& s)
		:_str(s._str)
	{
		++GetRefCount();
	}

	~String()
	{
		if (--GetRefCount() == 0)
		{
			cout << "delete" << endl;
			delete[](_str - 4);
		}
	}

	String& operator=(const String& s)
	{
		if (_str != s._str)
		{
			if (--GetRefCount() == 0)
			{
				delete[](_str - 4);
			}

			_str = s._str;
			GetRefCount()++;
		}
		return *this;
	}

	int& GetRefCount()
	{
		return *((int*)(_str - 4));
	}

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

	void CopyOnWrite()
	{
		if (GetRefCount()>1)//当一块空间有两个或者两个以上的对象指向时,才写时拷贝
		{
			char* tmp = new char[strlen(_str) + 5];
			strcpy(tmp, _str);
			--GetRefCount();
			_str = tmp;
			GetRefCount() = 1;
		}
	}

private:
	char* _str;
	int* _refCount;
};


以上是本人对两种不同方案写时拷贝的简单理解,希望看过的朋友可以提些改进的意见。

全部评论

相关推荐

Yushuu:你的确很厉害,但是有一个小问题:谁问你了?我的意思是,谁在意?我告诉你,根本没人问你,在我们之中0人问了你,我把所有问你的人都请来 party 了,到场人数是0个人,誰问你了?WHO ASKED?谁问汝矣?誰があなたに聞きましたか?누가 물어봤어?我爬上了珠穆朗玛峰也没找到谁问你了,我刚刚潜入了世界上最大的射电望远镜也没开到那个问你的人的盒,在找到谁问你之前我连癌症的解药都发明了出来,我开了最大距离渲染也没找到谁问你了我活在这个被辐射蹂躏了多年的破碎世界的坟墓里目睹全球核战争把人类文明毁灭也没见到谁问你了😆
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务