C++写时拷贝(测试用例String类)

       下面向大家介绍C++值拷贝(浅拷贝)、深拷贝、写时拷贝的过程与意义,以及异同点。我们都知道在C++中,每个类都有6个默认的成员函数,拷贝构造函数是很重要的其中一个,它是用一个已有的对象来构造一个新的对象出来。对于一般的类来说,例如日期类,它的成员变量只有一些简单的数据类型,整型,浮点型等拷贝构造函数就用简单的值拷贝,就是一一将成员变量拷贝过去。而对于像字符串类这样特殊的类来说,它的成员变量里面函数字符指针,即这个指针是指向一个常量字符串的;这时候我们写拷贝构造函数就要注意,如果我们还像写日期类那样,用值拷贝将变量一一拷贝过去的话,势必出现一个问题,就是会有两个对象指向同一块空间,那么最后对象在调用析构函数的时候会将那块用来存放字符串常量的空间会释放2次,而事实上同一块空间是不允许释放2次的。以上这种拷贝就叫做浅拷贝,也叫值拷贝。



        针对以上问题,我们就提出来深拷贝,即如果成员变量中如果含有指针变量,指针变量将会指向一块开辟好的空间,那么我们写拷贝构造的时候,就不是简单的值拷贝,而是再开辟一块一样的指针指向的空间,这样就避免了同一块空间会有是否2次的问题。


        深拷贝看似坚决了同一块空间是否两次的问题,但同时也带来了其他问题,比如说,每次都会开辟新空间,也是一种浪费,所以不是很高效,因此,我们有提出来一种叫做写时拷贝的浅拷贝法。
        写时拷贝是一种浅拷贝,对于拷贝出来的多个对象指向同一块空间,出现的一块空间多次析构问题,写时拷贝的解决方法是用引用计数,用来记录当前有多少个对象指向这块空间,多一个对象指向这块空间,引用计数加1,析构的时候判断出来对象自己本身看是否有其他对象指向这块空间,如果有,不释放,否则马上释放;还有一个问题就是,如果有几个对象指向一块空间(比如字符串常量),如果一个对象想改变这个空间的内容,而又不行影响其他指向这块空间的对象,写时拷贝的解决方法是,如果某个对象想改空间,那么它再创建一个一样的空间自己随便改,不要影响其他对象不想改的心情,但是这里注意的是原来空间的引用计数加1,而新创建的这块空间加一个引用计数。


下面是写时拷贝的例子:String类的简单实现

#include <iostream>
using namespace std;

class String
{
private:
	char* _str;
	int* _RefCount;
public:
	String(char* str="")//构造函数
		:_str(new char[strlen(str)+1])
		,_RefCount(new int(1))
	{
		strcpy(_str, str);
	}

	~String()//析构函数
	{
		if(--(*_RefCount)==0)//每次打算析构的时候,减掉一次引用计数
		{
			cout<<"~String()"<<endl;
			delete[] _str;
			delete _RefCount;
		}
	}

	String(const String& s)//拷贝构造函数
	{
		cout<<"String(const String& s)"<<endl;
		_str=s._str;
		_RefCount=s._RefCount;
		++(*_RefCount);//除了拷值,原来的引用计数加一,又多了一个对象指向这块空间
	}

	String& operator=(const String& s)//赋值运算符的重载
	{
		cout<<"String& operator=(const String& s)"<<endl;
		if(_str!=s._str)//防止指向同一块空间的两个对象赋值
		{
			if(--(*_RefCount)==0)//说明原来那块空间只有这一个对象,需要马上释放
			{
				delete[] _str;
				delete _RefCount;
			}
			_str=s._str;
			(*_RefCount)++;//另外这块空间多了一个对象指向它,引用计数加一
		}
		return *this;
	}
	

	char& operator[](size_t pos)
	{
		CopyOnWrite();//写时拷贝
		return _str[pos];
	}

	void CopyOnWrite()
	{
		if(*_RefCount>1)//只有2个或者2个以上的对象同时指向一块空间的时候,需要拷贝
		{
			char* tmp=new char[strlen(_str)+1];
			strcpy(tmp, _str);
			(*_RefCount)--;
			_str=tmp;
			_RefCount=new int(1);//新开辟的空间的引用计数
		}
	}

	char* GetStr()
	{
		return _str;
	}
};
        当然,上面这种引用计数是给这个引用计数也开辟一个独立的空间,即只要有对象指向一个新的空间,就有一个携带的引用计数空间,用来记录当前有多少个对象指向这块空间。还有一种比较省事的做法是将引用计数的空间和对象指向的这块空间开辟在一起,放在最前面的四个字节,这样无论是释放还是维护起来都比较简单,但是需要注意的是,在刚开始构造函数一进来,指针是指向空间最前面的,后面所操作的都是指向四个字节以后的,所以这个使用方面要注意。

下面是将引用计数和对象指向的空间开辟在一块的String类的例子 :

#include <iostream>
using namespace std;

class String
{
private:
	char* _str;
public:
	String(char* str="")
		:_str(new char[strlen(str)+5])
	{
		*((int*)_str)=1;//构造函数进来将引用计数初始化为1
		_str=_str+4;
		strcpy(_str, str);
	}

	//返回 引用,为以后可以改变
	int& GetRefCount()//获取 每个空间的引用计数,即有几个对象指向它
	{
		return *((int*)(_str-4));
	}

	String(const String& s)//拷贝构造函数
	{
		_str=s._str;
		++GetRefCount();
	}

	~String()//析构函数
	{
		if(--GetRefCount()==0)
		{
			cout<<"~String()"<<endl;
			delete[] (_str-4);
		}
	}

	String& operator=(const String& s)//赋值运算符的重载
	{
		if(_str!=s._str)
		{
			if(--GetRefCount()==0)
			{
				delete[] (_str-4);
			}
			_str=s._str;//改变指针的指向
			++GetRefCount();//引用计数加一
		}
		return *this;
	}

	char& operator[](size_t pos)
	{
		CopyOnWrite();
		return _str[pos];
	}

	void CopyOnWrite()
	{
		if(GetRefCount()>1)//当一块空间有两个或者两个以上的对象指向时,才写时拷贝
		{
			char* tmp=new char[strlen(_str)+5];
			strcpy(tmp, _str);
			--GetRefCount();
			_str=tmp;
			GetRefCount()=1;
		}
	}
	char* GetStr()//打印函数
	{
		return _str;
	}
};




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值