c++写时拷贝

拿String类举例:

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

	~String()
	{
		delete[] _str;
	}
private:
	char* _str;
};

void test1()
{
	String s1("abcd");
	for (size_t i = 0; i < 100; i++)
	{
		String s2(s1);
	}
}

可以看出来,这个类是没有写拷贝构造函数的,那么在test1的循环,这100次拷贝构造都是浅拷贝,首先来说什么是浅拷贝呢?


那么就会有两个问题:

1.两个对象的_str指向同一片空间,那么析构的时候这片空间必然会析构两次。

2.一个的改变会影响另一个。

所以程序必然会崩溃


那么写出拷贝构造函数,变成深拷贝可以解决这个问题。

但是这种循环多次的程序,如果写成深拷贝,那么每次都要重新开辟空间,效率不高。

为了解决问题,使用引用计数:

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

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


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

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

在类里多写一个int*型的成员变量,用来记录有多少对象指向同一片空间,并初始化为1,因为创建对象的时候就会有一个对象指向这片空间。

每次拷贝构造或者赋值运算符重载,就给(*refCount)加上1,表示多了一个对象指向这片空间。

那么拷贝构造如何写?赋值运算符重载怎么写?

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

	// s1 = s2
	String& operator =(const String& s)
	{
		if (this->_str != s._str)
		{
			if (--(*this->_refCount) == 0)
			{
				delete[] _str;
				delete _refCount;
			}

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



现在已经解决了析构多次的情况,只有当*refCount等于1的时候才会进行delete。

但是第二个问题呢,现在一个改变还是会影响另一个,比如operator[]

	char& operator[](size_t index)//可读,有可能可写
	{

		return _str[index];
	}
在测试函数里:

String s1("abc");

s1.[0] = 'x';

就会改变s1的值,相应的s2的值也会改变,所以需要写一个函数来改变s1的指向。

void String::CopyOnWrite()
{
	if (*_refCount > 1)//如果只有一个对象指向这片空间,那么可以直接修改,如果它的_refCount>1,有多个对象指向这片空间,\
							   那一个的改变会影响其他的对象,要对它处理
	{
		char* tmp = new char[strlen(this->_str)+1];
		strcpy(tmp, _str);
		_str = tmp;
		--(*_refCount);
		_refCount = new int(1);
	}
}


这样同样解决了一个对象的改变会影响另一个对象的问题。


接下来还有一种方法,我把它叫做多开4字节存数据的方法:

	class String
	{
	public:
		String(const char* str)
			:_str(new char[strlen(str) + 1 + 4])
		{
			*((int*)_str) = 1;//此时_str指向最前的地方,让他的前四个字节存有多少对象指向空间,初始化为1
			_str += 4;
			strcpy(_str, str);//前四个字节存数据,之后才存字符(串)
		}

	~String()
	{
		if (--(*((int*)(_str - 4))) == 0)
		{
			delete[] _str;
		}
	}

private:
	char* _str;
};

析构的时候是,如果只有一个对象指向这片空间,那么要delete[] _str-4,注意一点要给_str-4,不然前四个字节就释放不了,如果有多个对象指向这片空间,就给引用计数--。

那么拷贝构造与赋值运算符重载呢?

	String(String& s)
		:_str(s._str)
	{
		*((int*)(_str - 4)) += 1;
	}

	//						s1 = s2
	String& operator=(const String& s)
	{
		if (_str != s._str)
		{
			if (--(*((int*)(_str - 4))) == 0)
			{
				delete[] (_str-4);
			}

			_str = s._str;
			*((int*)(s._str - 4)) += 1;
		}
		return *this;
	}

解决一个改变影响另一个的问题,也是同样的思路。

 
 
 
 
void String::CopyOnWrite()
{
	if (--(*((int*)(_str - 4))) > 1)
	{
		char* tmp = new char[strlen(_str) + 1 + 4];
		tmp += 4;
		strcpy(tmp, this->_str);
		*((int*)(_str - 4)) -= 1;
		_str = tmp;
		*((int*)(_str - 4)) += 1;//_str已经指向新开辟的空间了
	}
}



现在的话,一个的改变已经不会影响另一个:

char& String::operator[](size_t index)
{
	CopyOnWrite();
	return _str[index];
}
void test1()
{
	String s1("abcd");
	String s2(s1);
	String s3("x");
	s3 = s1;

	s1[0] = 'y';
}
我写一个operator[],然后验证程序:



已经让s1,s2,s3的_str指向同一空间,然后执行s1[0] = 'y';


可以看到,s1的改变并没有影响s2和s3。

猜你喜欢

转载自blog.csdn.net/han8040laixin/article/details/78397677