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;
};

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

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



猜你喜欢

转载自blog.csdn.net/smile_zhangw/article/details/79333210
今日推荐