C++写时拷贝的不同方案(String类)
String类的写时拷贝,不同方案分析。
在使用String类时使用浅拷贝会产生一块空间析构多次的问题,所以产生了深拷贝,每次对对象进行值和空间同时拷贝,但这样会使用更多的空间。为了避免产生更多的空间,引入写时拷贝,当对空间进行更改时,检查是否有除自己外别的对象使用这块空间,若有,则自己重新开辟空间进行更改,不影响其他对象;若没有其他对象使用此空间,则说明只有自己使用此空间,就进行更改。此时引入引用计数,用于统计有多少对象在使用此空间。
方案1:
如下图所示:
当创建一个对象s1,再拷贝构造一个对象s2时,引用计数_refCount自动加1,此时为2。若此时s2要对自身进行修改,则s2重新开辟一块空间,检查引用计数_refCount是否大于1,若大于1,,则进行修改,这样就不会影响其他对象。
s2重新开辟一块儿空间,再修改s1原来空间的引用计数_refCount,使其减1,重新开辟的空间中因为此时只有s2一个对象,所以引用计数_refCount也为1.
小结:此方案的写时拷贝计数是同时开辟两块空间,一个自身存放的内容,一个存放引用计数_refCount,同时管理两块空间,统计当前使用此空间的对象数,当要修改当前空间的时候,进行对引用计数的判断,再决定是否开辟新的空间。
代码如下:
class String
{
public:
//构造函数
String(char* str="")
:_str(new char[strlen(str)+1])
,_refCount(new int(1))
{
strcpy(_str, str);
}
//拷贝构造函数
String(String& s)
{
_str = s._str;
_refCount = s._refCount;
++(*_refCount);
}
String& operator=(const String& s)
{
if (_str != s._str)
{
Release();
_str = s._str;
_refCount = s._refCount;
(*_refCount)++;
}
return *this;
}
void Release()
{
if (--(*_refCount) == 0)
{
cout << "delete" << endl;
delete[] _str;
delete _refCount;
}
}
~String()
{
Release();
}
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 pos)
{
CopyOnWrite();
return _str[pos];
}
char operator[](size_t pos) const
{
return _str[pos];
}
private:
char* _str;
int* _refCount;
};
方案2:
方案1中是开辟了两块空间进行管理,方案2采用开辟一块空间进行写时拷贝的操作。
开辟一块空间,在这块空间的头4个字节中放置引用计数,真正存放内容的空间从第5个字节开始。一次性多开辟4个字节进行写时拷贝的操作。
具体如下图所示:
当进行操作时,先检查引用计数的个数,然后进行判断是否开辟新的空间,同时修改引用计数的值,防止空间不能释放。
具体例子如下:
当创建了3个对象,s1,s2,s3同时指向一个空间,此时引用计数为3,再创建1个对象s4,s4的引用计数为1。
再进行操作s3 = s4;此时对应的引用计数和对象的指向都需要更改,更改之后如下图所示:
对象s3指向了s4,同时s3原来的空间的引用计数进行减1,新指向空间的引用计数进行加1.
小结:方案2的写时拷贝计数使用一块空间进行内容和引用计数的管理和操作,不开辟两块空间,方便管理。
代码如下:
class String
{
public:
String(const char* str)
:_str(new char[strlen(str) + 5])
{
_str += 4;
strcpy(_str, str);
GetRefCount();
}
String(const String& s)
:_str(s._str)
{
++GetRefCount();
}
~String()
{
if (--GetRefCount() == 0)
{
cout << "delete" << 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;
}
int& GetRefCount()
{
return *((int*)(_str - 4));
}
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;
}
}
private:
char* _str;
int* _refCount;
};
以上是本人对两种不同方案写时拷贝的简单理解,希望看过的朋友可以提些改进的意见。