【C++】写时拷贝(copy on write)与引用计数

  上一篇博客我们谈到了字符串的深拷贝与浅拷贝,在浅拷贝中,由于多个对象共用同一块内存空间,导致同一块空间被释放多次而出现问题,那能否保证:当多个对象共享一块空间时,该空间最终只释放一次呢?

这就是我们今天要说的引用计数

引用计数

原理:当多个对象共享一块资源时,要保证该资源只释放一次, 只需记录有多少个对象在使用该资源即可,每减少(增加)一个对象使用, 给该计数减一(加一),当最后一个对象不使用时,该对象负责将资源释放掉即可  

我们采用计数空间动态创建的方式来实现引用计数:所有对象不仅_str共用同一块空间,引用计数_pCount也共用同一块空间,如下:

//引用计数
class String
{
public:
	//构造函数,初始计数给成1
	String(const char* pStr="")
		:_pCount( new int(1))
	{
		if (NULL == pStr)
		{
			pStr = "";
		}
		_pStr = new char[strlen(pStr) + 1];
		strcpy(_pStr, pStr);
	}

	//拷贝构造函数,要让计数_pCount也共用同一块空间
	String(const String& s)
		:_pStr(s._pStr)
		,_pCount(s._pCount)
	{
		(*_pCount)++;
	}

	//s3=s1;--->s3必须和s1去共用空间
	//s3--->独立拥有空间---->计数为1--->计数要变成0--->必须释放自己的旧空间
	//s3--->与其他对象共享空间--->计数n--->计数要变成n-1--->不需要释放自己的空间
	//s1--->计数要加1
	String& operator=(const String& s)
	{
		if (this != &s)
		{
			release();
			_pStr = s._pStr;
			_pCount = s._pCount;
			(*_pCount)++;
		}
		return *this;
	}
	//运算符重载
	char& operator[](size_t index)
	{
		return _pStr[index];
	}
	//析构函数
	~String()
	{
		release();
	}
private:
	void release()
	{
		if (_pStr && (--(*_pCount) == 0))//每次释放空间的时候先检查引用计数是否为0,若为0进行空间的释放,若不为0不进行释放
		{
			delete[] _pStr;
			delete _pCount;
		}
		
	}
private:
	char* _pStr;
	int * _pCount;
};

int main()
{
	String s1("hello");
	String s2(s1);
	String s3;
	String s4(s3);
	s3 = s1;
	//s1[3] = 'j'; 出现问题,当一个对象的值进行改变时,和它同用一块空间的对象的值也会发生改变
	return 0;
}

这样空间多次释放的问题就可以解决,但又出现了以下问题:

1)空间浪费,解决方案,我们可以借鉴new[]的实现方式,在开辟_str时多开辟4个字节的空间,将_pCount存放在空间的前四个字节,如下图:


2)当一个对象的值进行改变时,其他对象的值也改变,解决方式是写时拷贝

写时拷贝

首先什么是写时拷贝,写时拷贝指的是当对象的值进行改变时,我们把这个对象单独分离出来,只对这个对象进行改变。

我们是通过重载[]运算符来实现的,但这个操作是线程不安全的,如下代码解决了上面引用计数的两个问题:

//引用计数,写时拷贝
class String
{
public:
	String(const char* pStr = "")
	{
		if (NULL == pStr)
		{
			pStr = "";
		}
		_pStr = new char[strlen(pStr) + 1 + 4];
		*((int*)_pStr) = 1;
		_pStr += 4;
		strcpy(_pStr, pStr);
	}

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

	String& operator=(const String& s)
	{
		if (this != &s)
		{
			release();
			_pStr = s._pStr;
			GetRefCount()++;
		}
		return *this;
	}

	//写时拷贝
	char& operator[](size_t index)
	{
		//若引用计数大于1,我们就要进行对象的分离
		if (GetRefCount() > 1)
		{
			//引用计数减1,进行分离
			--GetRefCount();
			String strTemp(_pStr);
			_pStr = NULL;
			swap(_pStr, strTemp._pStr);
		}
		return _pStr[index];
	}
	//当对象为const类型的时候调用这个[]运算符重载
	const char& operator[](size_t index)const
	{
		return _pStr[index];
	}

	//析构函数
	~String()
	{
		release();
	}

private:
	void release()
	{
		if (_pStr && (--(GetRefCount()) == 0))
		{
			delete[] (_pStr-4);
		}
	}
	//取出前四个字节(也就是引用计数)
	int& GetRefCount()
	{
		return *(int*)(_pStr - 4);
	}
private:
	char* _pStr;
};

int main()
{
	String s1("hello");
	String s2(s1);
	String s3;
	String s4(s3);
	s3 = s1;
	s1[2] = 'q';
	const String s5(s4);
	cout << s5[0] << endl;
	return 0;
}




猜你喜欢

转载自blog.csdn.net/zimituanzi_/article/details/80907373