首先,我们介绍浅拷贝,又浅拷贝推出深拷贝,引用计数拷贝以及写时拷贝

浅拷贝:

我们由下面的代码来找出浅拷贝存在的问题

  源代码:

class String
{
public:
	String(const char *pstr = "")
	{
		if(NULL == pstr)
		{
			_pstr = new char[1];
			*_pstr = '\0';
		}
		else
		{
			_pstr = new char[strlen(pstr)+1];
			strcpy(_pstr,pstr);
		}
	}
  String(const String &s)
		:_pstr(new char[strlen(s._pstr)+1])
	{
		if(this != &s)
		{
			_pstr = s._pstr;
		}
	}
	String& operator=(const String &s)
	{
		if(this != &s)
		{
			_pstr = s._pstr;
		}
		return *this;
	}
	~String()
	{
		if(NULL != _pstr)
		{
			delete[] _pstr;
			_pstr = '\0';
		}
	}
private:
	char *_pstr;
};
int main()
{
	String s1("hello");
	String s2("world");
	String s3(s1);
	return 0;
}
存在问题:

        若String类的成员函数存在指针时,在拷贝构造以及赋值运算符重载时 创建的对象和参数对象将会指向同一块目标空间,赋值符号左边的对象与也会指向右边对象的空间。即拷贝构造函数创建的新对象没有分配自己的空间,而赋值运算符重载会造成当前对象的原空间不能被找到,内存空间发生泄露。在程序结束时,两个String类 对象分别调用两次析构函数来释放同一个空间,会导致程序崩溃。


为了解决浅拷贝存在的问题,我们相对应有很多解决办法

一、深拷贝-----普通版

源代码:

class String
{
public:
	String(const char *pstr = "")
	{
		if(NULL == pstr)
		{
			_pstr = new char[1];
			*_pstr = '\0';
		}
		else
		{
			_pstr = new char[strlen(pstr)+1];
			strcpy(_pstr,pstr);
		}
	}
	String(const String & s)
		:_pstr(new char[strlen(s._pstr)+1])  //为String类对象重新生成一段新的空间
	{
		strcpy(_pstr,s._pstr);  //将s1对象的指针所指向的字符串拷贝到新创建的空间中去
	}
	String& operator=(const String& s)
	{
		if(this != &s)
		{
			char *_temp = new char[strlen(s._pstr)+1];  //首先创建一个临时的指针变量,
			strcpy(_temp,s._pstr);                      //将s对象的内容拷贝一份给临时变量
			delete[] _pstr;       //释放当前对象指针指向的空间
			_pstr = _temp;        //让当前对象指向临时变量指向的值
			_temp = NULL;         //将临时指针变量指针置空
		}
		return *this;
	}

	~String()
	{
		if(_pstr != NULL)
		{
			delete[] _pstr;
			_pstr = NULL;
		}
	}
private:
	char *_pstr;
};
int main()
{
	String s1("hello");
	String s2("world");
	String s3(s1); 
	String s4;
	s4 = s2;
	return 0;
}
二、深拷贝----简洁版
源代码:

#include<iostream>
using namespace std;
class String
{
public:
	String(const char *pstr = "")
	{
		if(NULL == pstr)
		{
			_pstr = new char[1];
			*_pstr = '\0';
		}
		else
		{
			_pstr = new char[strlen(pstr)+1];
			strcpy(_pstr,pstr);
		}
	}
	
	String(const String &s)
		:_pstr(NULL)  //添加该行语句才可运行成功,因为新创建的对象*this类是乱码,
	{                     //如果进行交换,将会导致temp._pstr内是乱码,在释放对象temp
		String temp(s._pstr);                       //即调用析构函数时,将会使temp
		swap(_pstr,temp._pstr);                    //中的乱码作为地址释放其内容,导致程序崩溃
	}
	String& operator=(const String &s)
	{
		if(this != &s)
		{
			String str(s);
			swap(_pstr,str._pstr);  
		}
		return *this;
	}

	String& operator=(String s)  //函数调用时传值而不是传引用,将会生成一个临时变量,与上方的operator=函数本质相同
	{
		if(this != &s)
		{
			swap(_pstr,s._pstr);
		}
		return *this;
	}

	~String()
	{
		if(_pstr != NULL)
		{
			delete[] _pstr;
			_pstr = NULL;
		}
	}
private:
	char *_pstr;
};
int main()
{
	String s1("hello");
	String s2("world");
	String s3(s1); 
	String s4;
	s4 = s2;
	return 0;
}
三、“引用计数”解决浅拷贝问题

 1.设置Sring类成员int  count,------失败
    原因:count是一个成员变量,即是一个临时变量,在不同的对象中不能累加
   2.设置String类成员为静态变量,static int count;----失败
     可以实现累加,但是对于每个对象来说,都有一个静态变量count,不能保证
     不同对象共用同一个变量count
   3.将引用计数设计成int*,完成String类-------成功
源代码:

class String
{
public:
	String(const char* pstr="")
		:_pcount(new int(1))
	{
		if(NULL == pstr)
		{
			_pstr = new char[1];
			*_pstr = '\0';
		}
		else
		{
			_pstr = new char[strlen(pstr)+1];
			strcpy(_pstr,pstr);
		}
	}

	String(const String& s)
		:_pstr(s._pstr)
		,_pcount(s._pcount)
	{
		++(*_pcount);
	}

	String& operator=(const String& s)
	{
		if(_pstr != s._pstr)
		{
			if(--(*_pcount)==0&&_pstr)
			{
				delete[] _pstr;
				delete _pcount;
			}
			_pstr = s._pstr;
			_pcount = s._pcount;
			++(*_pcount);
		}
		return *this;
	}


	//[]的重载
	char& operator[](size_t index)const  //const String s1("1111");char c =s1[0];const char c 
	{
		return _pstr[index];
	}

	const char& operator[](size_t index)const 
	{
		return _pstr[index];
	}

	~String()
	{
		if(--(*_pcount) == 0&&_pstr)
		{
			delete[]  _pstr;
			delete _pcount;
			_pstr = NULL;
			_pcount = NULL;
		}
	}
private:
	char *_pstr;
	int *_pcount;
};
四、写时拷贝(与深拷贝不同的是,我们采用new()函数的灵感,将计数空间放在我们开辟的字符串空间的上方偏移4字节)
我们应该知道在string类中,要实现写时才拷贝,需要解决两个问题,一个是内存共享, 一个是Copy-On-Wirte

转http://blog.csdn.net/shinehoo/article/details/5728995)

Copy-On-Write的原理是什么?

有一定经验的程序员一 定知道,Copy-On-Write一定使用了“引用计数”,是的,必然有一个变量类似于RefCnt。当第一个类构造时,string的构造函数会根据 传入的参数从堆上分配内存,当有其它类需要这块内存时,这个计数为自动累加,当有类析构时,这个计数会减一,直到最后一个类析构时,此时的RefCnt为 1或是0,此时,程序才会真正的Free这块从堆上分配的内存。
是的,引用计数就是string类中写时才拷贝的原理!

2.3.1、string类在什么情况下才共享内存的?

使 用别的类的数据时,无非有两种情况,1)以别的类构造自己,2)以别的类赋值。第一种情况时会触发拷贝构造函数,第二种情况会触发赋值操作符。这两种情况 我们都可以在类中实现其对应的方法。对于第一种情况,只需要在string类的拷贝构造函数中做点处理,让其引用计数累加;同样,对于第二种情况,只需要 重载string类的赋值操作符,同样在其中加上一点处理。

2.3.2、string类在什么情况下触发写时才拷贝(Copy-On-Write)

很显然,当然是在共享同一块内存的类发生内容改变时,才会发生Copy-On-Write。比如string类的 []、=、+=、+、操作符赋值,还有一些string类中诸如insert、replace、append等成员函数,包括类的析构时。

修改数据才会触发Copy-On-Write,不修改当然就不会改啦。这就是托延战术的真谛,非到要做的时候才去做。

源代码:

class String
{
public:
	String(const char *pstr = "")
	{
		if(NULL == pstr)
		{
			_pstr = new char[1+4];
			*(_pstr+4)= '\0';
		}
		else
		{
			_pstr = new char[strlen(pstr)+1];
			_pstr+=4;
			strcpy(_pstr,pstr);
		}
		getRef() = 1;
	}

	String(const String &s)
		:_pstr(s._pstr)
	{
		++getRef();
	}

	String& operator=(const String &s)
	{
		if(_pstr != s._pstr)
		{
			if(0 == --getRef())
			{
				delete[] (_pstr-4);
			}
			_pstr = s._pstr;
			++getRef();
		}
		return *this;
	}

	char& operator[](size_t index)  //返回值必须是引用,因为若是返回值,则会产生一个值的临时对象,具有常性
	{
		return *(_pstr+index);                      //这两个[]重载函数存在的问题是,若两个String类对象在同                                                              //一空间,则一个改变,另一个也会改变
	}
	const char& operator[](size_t index)const   //第二个const时还是可以对const String s1("1111")进行修改
	{                                           //返回引用则不能对s1的值改动
		return *(_pstr+index);
	}
	
	//写时拷贝--SWO--若s1、s2处于同一块空间,则改变s1的值必定会改变s2,写时拷贝则解决了这个问题,
	//在改变s1的情况下s2的值不变
	char& operator[](size_t index)
	{
		if((getRef()>1))
		{
			--getRef();
			char* pstr = new char[strlen(_pstr)+1+4];
			strcpy(pstr,_pstr);
			_pstr = pstr;
			++getRef();
		}
		return *(_pstr+index);
	}

	~String()
	{
		if(0 == --getRef())
		{
			delete[] (_pstr-4);
			_pstr = _pstr-4;
			_pstr = NULL;
		}
	}
private:
	char *_pstr;
	int& getRef()
	{
		return *((int*)_pstr-1);
	}
};
int main()
{
    String s1("hello");
	String s2(s1);
	String s3(s1);
	s1[0] = '1';
	return 0;
}





Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐