什么是写时拷贝
首先我们需要知道什么是写时拷贝,写时拷贝,通俗点说也就是写的时候拷贝。那么什么是写的时候拷贝呢,这又是什么意思呢?
举个例子,创建一个日期类的对象,然后又用这个对象拷贝构造了多个对象,也就是说这几个对象所指向的是同一块空间,那么当你对其中一个对象进行读操作的时候,什么问题都不会有,那么当你对某个对象进行写操作的时候,问题就出现了,一个对象的改变会影响其他对象,但是这并不是我们想要的,虽然说共用同一块空间,但是对象是独立的。我的改变应该不会影响别人。那么为了解决这个问题,我们就引入了写时拷贝这个概念,就是说,当你要对某个对象进行写才做,而这个对象又与其他对象共用同一块空间,此时,就需要,再重新开一段空间,把你要进行写操作的那个对象拷贝过来,然后再进行写操作,这样就不会影响其他的对象了。
下面一段代码来解释一下
#include<iostream>
using namespace std;
class String
{
private:
char* _str;
public:
String(char* str = "")
:_str(new char [strlen(str)+1])
{
strcpy(_str,str);
cout<<"String(char*)"<<endl;
}
String(const String&str)
:_str(str._str)
{
cout<<"String(const &)"<<endl;
}
~String()
{
if(_str!=NULL)
delete []_str;
_str = NULL;
cout<<"~String"<<endl;
}
};
int main()
{
String s1("qwer");
String s2(s1);
String s3(s2);
return 0;
}
运行结果如下图
图中我们可以看到,三个对象的地址是相同的,也就是是,指向同一块空间,但是按照常规的特点,三个对象应该是要析构三次,但是在这里,因为是只有一块空间,析构一次后,再次析构系统就崩溃了。为了解决这个问题我们引入了引用计数,就是可以定义一个变量用来保存一块空间对象的个数,当要进行写操作的时候就按照上面所说的方法,先拷贝再写,但是需要改变引用计数。这样就可以实现写时拷贝了。
写时拷贝的代码(引用计数)
class String
{
private:
char *_str;
int *_refCountPtr;
int _capacity;
int _size;
public:
String(char *str = "")
:_refCountPtr(new int(1))
{
_capacity = strlen(str);
_size = _capacity;
_str = new char[_capacity+1];
strcpy(_str,str);
cout<<"String(char *str = "")"<<endl;
}
String(String&s)
:_str(s._str)
,_capacity(0)
,_size(0)
{
_refCountPtr = s._refCountPtr;//改变引用计数
(*_refCountPtr)++;
cout<<"String(String&s)"<<endl;
}
String &operator = (String&s)
{
cout<<"operator="<<endl;
if(_str!=s._str)//自己给自己赋值
{
if((*_refCountPtr)==1)
{
delete[]_str;
delete _refCountPtr;
}
_str = s._str;
_refCountPtr = s._refCountPtr;
(*_refCountPtr)++;
}
return *this;
}
~String()
{
Release();
cout<<"~String()"<<endl;
}
};
还有另外一种方法来实现写时拷贝,具体思想就是,在构造对象的时候就为它多开辟四个字节用来存引用计数,这样就不需要变量了,要用引用计数的的时候只需要把它取出来就可以了。
写时拷贝(指针)
class String
{
private:
char* _pStr;
public:
String(const char* pStr = "")
{
if(NULL == pStr)//传了一个空字符串
{
_pStr = new char[1 + 4];//用了一个指针,多四个字节
_pStr = (char*)(((int*)_pStr)+1);//向后走四个字节
*_pStr = '\0';
}
else
{
_pStr = new char[strlen(pStr) + 1 + 4];
_pStr = (char*)(((int*)_pStr)+1);
strcpy(_pStr, pStr);
}
*((int*)_pStr - 1) = 1;//引用计数初始化为1
}
String(const String&s)//直接拷贝,让引用计数加加就好,后面的事析构会做
:_pStr(s._pStr)
{
++GetCount();
}
String& operator=(const String& s)
{
if(this != &s)//判断是否自己给自己赋值
{
Release();//因为这里要析构,一般我们不会显示的调用析构,所以封装一个函数来代替
_pStr = s._pStr;
++GetCount();
}
return *this;
}
~String()
{
Release();
}
private:
int& GetCount()const
{
return *((int*)_pStr - 1);//把引用计数取出来
}
void Release()
{
if(_pStr && (0 ==GetCount()--))
{
_pStr = (char*)((int*)_pStr-1);
delete[] _pStr;
_pStr = NULL;
}
}
};
上面这两种方法都可以实现写时拷贝。