String类的写时拷贝

 假设我们现在有这样一个类,当我们只是为了查询时,实现拷贝构造函数时就可以实现浅拷贝;仅当我们要对生成的对象进行修改时,才进行深拷贝,这样可以很节省空间,也能提高效率。可是如果这样的话,多个浅拷贝,析构时就会导致程序崩溃。因此我们可以自己实现一个析构函数,引入引用计数,当有新的对象生成时,引用计数置为零;之后每拷贝构造产生一个新的对象时,我们使用浅拷贝,引用计数加一;析构函数首先引用计数减一,然后判断引用计数的值是否等于零,如果等于零则delete,否则什么也不做。我们将引用计数设计在内存中,即每次new的时候都多给出四个字节的空间来存放引用计数这个整形数字。放在所有申请的空间的头部。

写时拷贝:对于private成员带有指针的类,如果不想改变其中的值,可以考虑实现浅拷贝,但是这样的话就会在析构的时候出现问题,所以需要一个计数器getRefcount()看有多少个指针指向了同一块内存区域,把析构函数的内部写一个析构减一的函数Release(),检测到有多个指针指向同一个区域时,先减一,判断是否为零,最后析构。如果打算改其中的值,再实现深拷贝,调用计数器对新申请的内存块指针个数重新计数。

由于我们要修改值时一定会调用[],所以我们在[]运算符的重载中实现深拷贝。

class String
{
public:
	String(char *p = "") :_str(new char[strlen(p) + 5]()) //此处加五是先加四字节的整型数据再加一个'\0'
	{
		_str += 4; //表示前四个字节用来存放引用计数,之后的才放其他数据
		strcpy(_str, p);
		getRefcount() = 1;
	}

	String(const String& rhs) :_str(rhs._str) //拷贝构造,加引用计数
	{
		++getRefcount();
	}

	String& operator=(const String& rhs)  //赋值运算符重载,加引用计数
	{
		if (this != &rhs)  //自赋值判断
		{
			Release(); //this  _str
			_str = rhs._str;
			++getRefcount();
		}
		return *this;
	}

	char& operator[](int index) //调用中括号的时候才会出现修改的情况,所以实现中括号的重载时实现深拷贝
	{
		if (getRefcount() != 1)
		{
			char* pnewstr = new char[strlen(_str) + 5]();
			pnewstr += 4;
			strcpy(pnewstr, _str);
			--getRefcount();
			_str = pnewstr;
			getRefcount() = 1;
		}
		return _str[index];
	}

	~String()
	{
		Release();
	}

private:

	void Release()
	{
		if (--getRefcount() == 0)
		{
			delete (_str - 4);
		}
		_str = NULL;
	}

	int& getRefcount()
	{
		return *(int *)(_str - 4); //返回引用计数的值
	}

	char* _str;
};

int main()
{
	String str1("hello");
	String str2(str1);
	String str3("world");
	str3 = str1;
	str1[0] = 'a';
	return 0;
}

 但是按照如上方案会出现一些问题,即我们如果只调用中括号查询,也会进行深拷贝,造成资源不必要的浪费。事实上,我们stl库里面实现的时候也是考虑到这一点,所以不同的stl标准库实现不同, 比如 Centos 6.5 默认的 stl::string 实现就是 『copy-on-write』(写时拷贝), 而 Mac OS X (10.10.5) 实现就是 『eager-copy』(深拷贝)。

       

猜你喜欢

转载自blog.csdn.net/shang_12266029/article/details/84567153
今日推荐