上一篇博客我们谈到了字符串的深拷贝与浅拷贝,在浅拷贝中,由于多个对象共用同一块内存空间,导致同一块空间被释放多次而出现问题,那能否保证:当多个对象共享一块空间时,该空间最终只释放一次呢?
这就是我们今天要说的引用计数
引用计数
原理:当多个对象共享一块资源时,要保证该资源只释放一次, 只需记录有多少个对象在使用该资源即可,每减少(增加)一个对象使用, 给该计数减一(加一),当最后一个对象不使用时,该对象负责将资源释放掉即可
我们采用计数空间动态创建的方式来实现引用计数:所有对象不仅_str共用同一块空间,引用计数_pCount也共用同一块空间,如下:
//引用计数
class String
{
public:
//构造函数,初始计数给成1
String(const char* pStr="")
:_pCount( new int(1))
{
if (NULL == pStr)
{
pStr = "";
}
_pStr = new char[strlen(pStr) + 1];
strcpy(_pStr, pStr);
}
//拷贝构造函数,要让计数_pCount也共用同一块空间
String(const String& s)
:_pStr(s._pStr)
,_pCount(s._pCount)
{
(*_pCount)++;
}
//s3=s1;--->s3必须和s1去共用空间
//s3--->独立拥有空间---->计数为1--->计数要变成0--->必须释放自己的旧空间
//s3--->与其他对象共享空间--->计数n--->计数要变成n-1--->不需要释放自己的空间
//s1--->计数要加1
String& operator=(const String& s)
{
if (this != &s)
{
release();
_pStr = s._pStr;
_pCount = s._pCount;
(*_pCount)++;
}
return *this;
}
//运算符重载
char& operator[](size_t index)
{
return _pStr[index];
}
//析构函数
~String()
{
release();
}
private:
void release()
{
if (_pStr && (--(*_pCount) == 0))//每次释放空间的时候先检查引用计数是否为0,若为0进行空间的释放,若不为0不进行释放
{
delete[] _pStr;
delete _pCount;
}
}
private:
char* _pStr;
int * _pCount;
};
int main()
{
String s1("hello");
String s2(s1);
String s3;
String s4(s3);
s3 = s1;
//s1[3] = 'j'; 出现问题,当一个对象的值进行改变时,和它同用一块空间的对象的值也会发生改变
return 0;
}
这样空间多次释放的问题就可以解决,但又出现了以下问题:
1)空间浪费,解决方案,我们可以借鉴new[]的实现方式,在开辟_str时多开辟4个字节的空间,将_pCount存放在空间的前四个字节,如下图:
2)当一个对象的值进行改变时,其他对象的值也改变,解决方式是写时拷贝
写时拷贝
首先什么是写时拷贝,写时拷贝指的是当对象的值进行改变时,我们把这个对象单独分离出来,只对这个对象进行改变。
我们是通过重载[]运算符来实现的,但这个操作是线程不安全的,如下代码解决了上面引用计数的两个问题:
//引用计数,写时拷贝
class String
{
public:
String(const char* pStr = "")
{
if (NULL == pStr)
{
pStr = "";
}
_pStr = new char[strlen(pStr) + 1 + 4];
*((int*)_pStr) = 1;
_pStr += 4;
strcpy(_pStr, pStr);
}
String(const String& s)
:_pStr(s._pStr)
{
++GetRefCount();
}
String& operator=(const String& s)
{
if (this != &s)
{
release();
_pStr = s._pStr;
GetRefCount()++;
}
return *this;
}
//写时拷贝
char& operator[](size_t index)
{
//若引用计数大于1,我们就要进行对象的分离
if (GetRefCount() > 1)
{
//引用计数减1,进行分离
--GetRefCount();
String strTemp(_pStr);
_pStr = NULL;
swap(_pStr, strTemp._pStr);
}
return _pStr[index];
}
//当对象为const类型的时候调用这个[]运算符重载
const char& operator[](size_t index)const
{
return _pStr[index];
}
//析构函数
~String()
{
release();
}
private:
void release()
{
if (_pStr && (--(GetRefCount()) == 0))
{
delete[] (_pStr-4);
}
}
//取出前四个字节(也就是引用计数)
int& GetRefCount()
{
return *(int*)(_pStr - 4);
}
private:
char* _pStr;
};
int main()
{
String s1("hello");
String s2(s1);
String s3;
String s4(s3);
s3 = s1;
s1[2] = 'q';
const String s5(s4);
cout << s5[0] << endl;
return 0;
}