【c++】深入解析浅拷贝与深拷贝

浅拷贝

浅拷贝,也称位拷贝,编译器只是将对象中的值拷贝过来,如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进行操作时就会出现访问违规

先看一段代码

class String
{
public :
	String(const char *ptr="")//构造函数。默认放\0
		:_ptr(new char[strlen(ptr)+1])//strlen计算长度不加\0
	{
		strcpy(_ptr,ptr);
	}

	~String()
	{
		if(_ptr)
		{
			cout<

执行结果如下:

【c++】深入解析浅拷贝与深拷贝_第1张图片

可以发现这里程序会崩溃,什么原因导致崩溃

【c++】深入解析浅拷贝与深拷贝_第2张图片

这就是浅拷贝,多个对象共享同一份资源,造成的问题也显而易见,一份资源被释放了多次。

深拷贝

深拷贝就是给所构造的对象重新申请了一段空间

class String
{
public:
	String(const char *str="")
	{
		if(str==NULL)
			str="";
		_pStr=new char[strlen(str)+1];
		strcpy(_pStr,str);
	}
	String(const String& s)//深拷贝
		:_pStr(new char[strlen(s._pStr)+1])//重新申请了一段空间
	{
		strcpy(_pStr,s._pStr);
	}

	//赋值运算符重载
	//方法一
	//String& operator=(const String s)
	//{
	//	if(this !=	&s)
	//	{
	//		delete[] _pStr;
	//		_pStr=new char[strlen(s._pStr )+1];
	//		strcpy(_pStr,s._pStr);
	//	}
	//	return *this;//为了支持链式访问
	//}

	//方法二(优)
	String& operator=(const String& s)
	{
		if(this!=&s)//自己不能拷贝自己
		{
			char *tmp=new char[strlen(s._pStr )+1];
			strcpy(tmp,s._pStr );
			_pStr=tmp;

		}
		return *this;
	}

	~String()
	{
		if(_pStr)
		{
			delete[] _pStr;
		}
	}

private:
	char *_pStr;
};

int main()
{
	String s1("hello");
	String s2(s1);
	return 0;
}

【c++】深入解析浅拷贝与深拷贝_第3张图片

 

一般情况下,上面对赋值运算符重载的两种写法都可以,但是相对而言,第二种更优一点,对于第一种,先释放了旧空间,但是如果下面用new开辟新空间时有可能失败,抛出异常而这时将s2赋值给s3,不仅没有赋值成功,而且也破坏了原有的s3对象,对于第二种,先开辟新空间,将新空间赋给一个临时变量,就算这时空间开辟失败,也不会影响原本s3对象

引用计数

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

class String
{
public:
	String()
	{}

	String(const char *str)
	{
		if(str==NULL)
		{
			_str=new char[4+1];//多申请一个int来存储计数器
			_str+=4;
			_str='\0';
		}
		else
		{
			_str=new char[strlen(str)+1+4];
			_str+=4;
			strcpy(_str,str);
		}
		GetCount(_str)=1;//将计数器初始值设为1
	}
	String(const String& s)
		:_str(s._str)
	{
		GetCount(_str)++;
	}

	//s1=s2;
	String operator=(const String& s)
	{
		if(_str!=s._str )
		{
			//1.当s1的引用计数为1时
			//a.释放空间
			//b.改变s1指针的指向
			//c.s2的引用计数加1
			Release();
			//当s1的引用计数大于1时
			//a.s1引用计数减1
			//b.同上b,c
			_str=s._str ;
			++GetCount(_str);//引用计数加1
		}
		return *this;
	}

	~String()
	{
		Release();
	}
private:
	int& GetCount(char * str)
	{
		return *(int *)(str-4);//因为这块内存类型为char
	}

	void Release()
	{
		if(_str!=NULL && (--GetCount(_str))==0)
			delete[] (_str-4);//一定要释放存储计数器的空间
	}
private:
	char *_str;
};

int main()
{
	String s1("hello");
	String s2(s1);

	String s3("world");
	String s4(s3);
	String s5(s3);
	return 0;
}

上面的代码是在构造的时候多申请4字节空间来存储计数器,从而实现计数

【c++】深入解析浅拷贝与深拷贝_第4张图片

但是这样还是有问题,看下面的代码

class String
{
public:
	String()
	{}

	String(const char *str)
	{
		if(str==NULL)
		{
			_str=new char[4+1];//多申请一个int来存储计数器
			_str+=4;
			_str='\0';
		}
		else
		{
			_str=new char[strlen(str)+1+4];
			_str+=4;
			strcpy(_str,str);
		}
		GetCount(_str)=1;//将计数器初始值设为1
	}
	String(const String& s)
		:_str(s._str)
	{
		GetCount(_str)++;
	}

	//s1=s2;
	String operator=(const String& s)
	{
		if(_str!=s._str )
		{
			//1.当s1的引用计数为1时
			//a.释放空间
			//b.改变s1指针的指向
			//c.s2的引用计数加1
			Release();
			//当s1的引用计数大于1时
			//a.s1引用计数减1
			//b.同上b,c
			_str=s._str ;
			++GetCount(_str);//引用计数加1
		}
		return *this;
	}

	char& operator[](size_t index)//可以用下标的方式来访问String类
	{
		return _str[index];
	}
	~String()
	{
		Release();
	}
private:
	int& GetCount(char * str)
	{
		return *(int *)(str-4);//因为这块内存类型为char
	}

	void Release()
	{
		if(_str!=NULL && (--GetCount(_str))==0)
			delete[] (_str-4);//一定要释放存储计数器的空间
	}
private:
	char *_str;
};

int main()
{
	String s1("hello");
	String s2(s1);

	String s3(s1);
	s3[1]='a';
	return 0;
}

当共用同一块空间的对象的任一对象修改字符串中的值,则会导致所有共用这块空间中的内容全被改变,我们只想改变s3的值,但是和它共用的两个对象的值也全改变,这就引出了写时拷贝

写时拷贝

有多个对象共享同一空间时,当对其中一个对象只读时,不会有什么影响,但是想要改变某个对象的值时,这时就要为这个对象重新分配空间,代码实现如下

class String
{
public:
	String()
	{}

	String(const char *str)
	{
		if(str==NULL)
		{
			_str=new char[4+1];//多申请一个int来存储计数器
			_str+=4;
			_str='\0';
		}
		else
		{
			_str=new char[strlen(str)+1+4];
			_str+=4;
			strcpy(_str,str);
		}
		GetCount()=1;//将计数器初始值设为1
	}
	String(const String& s)
		:_str(s._str)
	{
		GetCount()++;
	}

	//s1=s2;
	String operator=(const String& s)
	{
		if(_str!=s._str )
		{
			
			Release();
			_str=s._str ;
			++GetCount();//引用计数加1
		}
		return *this;
	}

	char& operator[](size_t index)//可以用下标的方式来访问String类
	{
		if(GetCount()>1)
		{
			--GetCount();
			char* pTemp=new char[strlen(_str)+1+4];
			pTemp+=4;
			strcpy(pTemp,_str);
			_str=pTemp;
			GetCount()=1;//将新空间置1
		}
		return _str[index];
	}


	~String()
	{
		Release();
	}
private:
	int& GetCount()
	{
		return *(int *)(_str-4);//因为这块内存类型为char
	}

	void Release()
	{
		if(_str!=NULL && (--GetCount())==0)
			delete[] (_str-4);//一定要释放存储计数器的空间
	}
private:
	char *_str;
};

int main()
{
	String s1("hello");
	String s2(s1);

	String s3(s1);
	s3[1]='a';
	return 0;
}

你可能感兴趣的:(【c++】深入解析浅拷贝与深拷贝)