写时拷贝技术
写时拷贝技术实际上是一种拖延战术,是为了提高效率而产生的技术,这怎么提高效率呢?实际上就是在需要开辟空间时,假装开了空间,实际上用的还是原来的空间,减少开辟空间的时间,等到真正要使用新空间的时候才去真正开辟空间。
举一个例子理解写时拷贝技术:我们小时候经常会遇到这种情况:家里面有哥哥姐姐的,大人们经常会让我们穿哥哥姐姐穿剩的衣服,这样看起来我们就有了新衣服,但实际上我们穿的还是哥哥姐姐的旧衣服,等到我们真的长大了,才会给我们买属于自己的新衣服,这样节省了给我们买衣服的时间和财力。从而节省了很多资源(提高效率)。等我们真的需要时才不得不买新衣服(拖延战术)。
写时拷贝技术原理
写时拷贝技术实际上是运用了一个 “引用计数” 的概念来实现的。在开辟的空间中多维护四个字节来存储引用计数。
有两种方法:
①:多开辟四个字节(pCount)的空间,用来记录有多少个指针指向这片空间。
②:在开辟空间的头部预留四个字节的空间来记录有多少个指针指向这片空间。
当我们多开辟一份空间时,让引用计数+1,如果有释放空间,那就让计数-1,但是此时不是真正的释放,是假释放,等到引用计数变为 0 时,才会真正的释放空间。如果有修改或写的操作,那么也让原空间的引用计数-1,并且真正开辟新的空间。
linux 下的 fork() 就是用的写时拷贝技术,引用计数不光在 string 这里用到,还有智能指针 shared_ptr 也用到了引用计数来解决拷贝问题。
举个例子
string 的写时拷贝(维护一个指针):
class String
{
public:
//构造
String(const char* str)
:_str(new char[strlen(str) + 1])
,_pCount(new int(1))
{
strcpy(_str, str);
}
//拷贝构造
String(const String& s)
:_str(s._str)
,_pCount(s._pCount)
{
(*_pCount)++;
}
//赋值运算符重载
String& operator=(const String& s)
{
if(_str != s._str)
{
if(--(*_pCount) == 0)
{
delete[] _str;
delete _pCount;
}
_str = s._str;
_pCount = s._pCount;
(*_pCount)++;
}
return *this;
}
~String()
{
if(--(*_pCount) == 0)
{
delete[] _str;
delete _pCount;
}
}
char& operator[](size_t pos)
{
if(*_pCount > 1)
{
char* newstr = new char[strlen(_str) + 1];
strcpy(newstr, _str);
--(*_pCount);
_str = newstr;
_pCount = new int(1);
}
return _str[pos];
}
const char* c_str()
{
return _str;
}
private:
char* _str;
int* _pCount;
};
源码中的写法:在空间的头部维护四个字节的空间,记录引用的个数。放在头部维护效率能高一些,如果放在尾部维护的话,每次开辟新的空间都要讲这四个字节也向后挪动相应的位置,所以放在前面效率高点
class String
{
public:
//构造
String(const char* str)
:_str(new char[strlen(str) + 4 + 1])
{
_str += 4; //前四个字节放引用计数
strcpy(_str, str);
GetRefCount() = 1;
}
//拷贝构造
String(const String& s)
:_str(s._str)
{
GetRefCount()++;
}
//赋值运算符重载
String& operator=(const String& s)
{
if(_str != s._str)
{
if(--GetRefCount() == 0)
{
delete[] (_str - 4);
}
_str = s._str;
GetRefCount()++;
}
return *this;
}
~String()
{
if(--GetRefCount() == 0)
{
delete[] (_str - 4);
_str = nullptr;
}
}
char& operator[](size_t pos)
{
if(GetRefCount() > 1)
{
--GetRefCount();
char* newstr = new char[strlen(_str) + 4 + 1];
newstr += 4;
strcpy(newstr, _str);
_str = newstr;
GetRefCount() = 1;
}
return _str[pos];
}
int& GetRefCount()
{
return *((int*)(_str - 4)); //前四个字节为引用计数
}
private:
char* _str;
};