String引用技术写时拷贝

  浅拷贝&深拷贝 

   string类的赋值浅拷贝会使多个对象指向同一块空间,当调用析构函数时会使一块空间释放多次,导致程序崩溃。再进一步我们会想到深拷贝,调用拷贝构造或赋值时会拷贝一块新的空间,并将值拷贝下来,这样各自指向自己的数据块,析构时释放各自的数据块。但由于不断的开辟空间、释放空间会花费时间,而且当创建对象较多时,会占用大量的内存。那怎样去避免这样的问题呢?

 

    我们知道string类的浅拷贝使多个对象指向一块空间 ,为了让析构时这块空间不被释放多次,我们可以在这块空间上加上引用技术(表示此时有几个对象指向这块空间),当我需要写时才去开辟新的空间。这就是引用技术

 

写时拷贝

 

 

       写时拷贝技术可以理解为“写的时候才去分配空间”,这实际上是一种拖延战术。

      原理:

           写时拷贝技术是通过"引用计数"实现的,在分配空间的时候多分配4个字节,用来记录有多少个指针指向块空间,当有新的指针指向这块空间时,引用计数加一,当要释放这块空间时,引用计数减一(假装释放),直到引用计数减为0时才真的释放掉这块空间。当有的指针要改变这块空间的值时,再为这个指针分配自己的空间(注意这时引用计数的变化,旧的空间的引用计数减一,新分配的空间引用计数加一)。

 

   此时我们知道要使用写时拷贝技术实现String类需要加上引用技术,那我们又如何添加引用技术呢?   

  如图,三种方案参考,那种合理呢?

 

 

分析:    

     方案1:_refCount存放于String中,那么每个实例中都有一个不能实现共享。

     方案2:_refCount为静态成员,所用的String共享,不由类的构造函数初始化,而对象的创建需要调用构造函数,所以它无法计数到正在使用同一块空间的对象的个数。

    方案3:_refCount为指针类型,当然可以实现共享(当维护这块空间不同),进一步我们可以考虑将_refCount放在_str开始或结束的四个字节中。(放在开头位置,增容不需要移动,如果放在结束位置的话,增容时需要移动计数的位置,所以这里将介绍放于头部的写法)

 

String的实现

 

 

         方法一:

class String
{
public:
	//构造函数
	String(const char* str = "") //构造函数的初始化列表
		:_str(new char[strlen(str)+1])
		,_refcount(new int(1))
	{
		strcpy(_str,str);
	}

	//拷贝构造
	//s2(s1)
	String(String& s)
	{
		_str = s._str;
		_refcount = s._refcount ;
		++(*_refcount);
	}

	//s1 = s2
	String& operator=(const String& s)
	{
		if(_str != s._str)
		{
			Release(); //将s1的指向释放,
			_str = s._str;
			_refcount = s._refcount ;
			++(*_refcount);
		}
		return *this;
	}

	void CopyOnWrite()
	{
		if(*_refcount > 1)
		{
			char* tmp = new char[strlen(_str)+1];
			strcpy(tmp,_str);
			--(*_refcount);
			_str = tmp;
			_refcount = new int(1);
		}
	}
		
	char& operator[](size_t index)
	{
		CopyOnWrite();
		return _str[index];
	}

	~String()
	{
		Release();
	}
	
	void Release()
	{
		if((--_refcount)==0)
		{
			delete [] _str;
		}
	}

	char* C_Str()
	{
		return _str;
	}
private:
	char* _str;
    int* _refcount;//引用的次数(引用技术) 创一块空间配一个引用技术
};

 

 

    由结果可以看出,s1,s2,s3指向同一块空间。

测试2:

 

 

当对s3进行写时,s3的地址改变(对其进行拷贝)

方法二:

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

	String(String& s)
		:_str(s._str )
	{
		GetRefCount ()++;
	}

	~String()
	{
		Release ();
	}

	String& operator= (const String& s)
	{
		if(_str != s._str )
		{
			Release();
			_str = s._str ;
			++GetRefCount ();
		}
		return *this;
	}

	void CopyOnWrite()
	{
		if(GetRefCount()>1 )
		{
			char* tmp = new char[strlen(_str)+1+5];
			strcpy(tmp+4,_str);
			--GetRefCount ();	
			_str = tmp;
		    _str+=4;
			GetRefCount () = 1;
		}
	}

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

	int& GetRefCount()
	{
		return *((int*)(_str-4));
	}

	void Release()
	{
		if(--GetRefCount () == 0)
		{
			delete[] (_str-4);
		}
	}

	char* C_Str()
	{
		return _str;
	}
private:
	char* _str;
};

 

     这里不再对方法二进行测试 

 

使用写时拷贝技术可以减少开辟空间的消耗,提高程序的性能。但同时存在线程安全的问题。

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值