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;
};


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

全部评论

相关推荐

沉淀一会:1.同学你面试评价不错,概率很大,请耐心等待; 2.你的排名比较靠前,不要担心,耐心等待; 3.问题不大,正在审批,不要着急签其他公司,等等我们! 4.预计9月中下旬,安心过节; 5.下周会有结果,请耐心等待下; 6.可能国庆节前后,一有结果我马上通知你; 7.预计10月中旬,再坚持一下; 8.正在走流程,就这两天了; 9.同学,结果我也不知道,你如果查到了也告诉我一声; 10.同学你出线不明朗,建议签其他公司保底! 11.同学你找了哪些公司,我也在找工作。
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务