首先就深浅拷贝的问题做一个解释;
所谓浅拷贝,也称位拷贝,就是在类中拷贝构造函数以及赋值运算符重载时。通过直接将指针的值拷贝,与原对象共用一个空间;
而深拷贝,在以所述的两种函数中重新申请一块空间存放新的对象;
系统自动生成的拷贝构造即为浅拷贝;
class String
{
public:
String(const char* pStr = "")
{
if (NULL == pStr)
{
_pStr = new char[1];
*_pStr = '\0';
}
else
{
_pStr = new char[strlen(pStr) + 1];
strcpy(_pStr, pStr);
}
}
String(const String &s)
:_pStr(s._pStr)
{
}
String& operator=(const String& s)
{
if (this != &s)
{
delete[]_pStr;
_pStr = s._pStr;
}
return *this;
}
~String()
{
delete[]_pStr;
_pStr = NULL;
}
private:
char* _pStr;
};
在这种浅拷贝的情况下,不难发现当析构时由于浅拷贝会对同一块空间两次释放,为了避免这种情况,我们对这种浅拷贝进行改造:带有引用计数,用指针指向引用计数的空间;
private:
char* _pStr;
int* _pCount;
添加一个新的成员:一个整型指针用来存放当前指向同一空间的对象个数
首先是构造函数与拷贝构造函数:
String(const char* pStr = "")//构造
:_pCount(new int[1])
{
if (NULL == pStr)
{
_pStr = new char[1];
*_pStr = '\0';
}
else
{
_pStr = new char[strlen(pStr) + 1];
strcpy(_pStr, pStr);
}
*_pCount=1;
}
String(const String &s)//拷贝构造
:_pStr(s._pStr)
, _pCount(s._pCount)
{
(*_pCount)++;//注意优先级
}
赋值运算符重载时,需注意原对象的空间是否还有其他对象在使用,若无便释放,若有则计数减一;
代码如下:
String& operator=(const String& s)
{
if (this != &s)
{
if (0 == --(*_pCount))
{
delete[]this;
}
_pStr = s._pStr;
_pCount = s._pCount;
(*_pCount)++;
}
return *this;
}
最后是析构函数,检测计数是否为一,判断是否释放空间
~String()
{
if (0 == --(*_pCount))
{
delete[]_pStr;
delete[]_pCount;
_pStr = NULL;
_pCount = NULL;
}
}
这种使用计数指针的方法可以避免浅拷贝的出错,但多出一个指针成员终归是不理想的,参考new[]的做法,我们可以将这个计数放在所申请的空间前,只需多申请4个字节以及做一些处理;
class String
{
public:
String(const char* pStr = "")
:_pStr(new char[strlen(pStr) + 4+1])
{
*((int *)_pStr) = 1;
_pStr += 4;
strcpy(_pStr, pStr);
}
String(const String &s)
:_pStr(s._pStr)
{
++(*((int *)(_pStr - 4)));
}
String& operator=(const String& s)
{
if (this != &s)
{
if (0 == --(*((int *)(_pStr - 4))))
{
_pStr -= 4;
delete[] _pStr;
}
_pStr = s._pStr;
++(*((int *)(_pStr - 4)));
}
return *this;
}
~String()
{
if (0 == --(*((int *)(_pStr - 4))))
{
_pStr -= 4;
delete[]_pStr;
_pStr = NULL;
}
}
private:
char* _pStr;
};
下面介绍深拷贝
class String
{
public:
String(const char* pStr = "")
{
if (NULL == pStr)
{
_pStr = new char[1];
*_pStr = '\0';
}
else
{
_pStr = new char[strlen(pStr) + 1];
strcpy(_pStr, pStr);
}
}
String(const String &s)
:_pStr(new char[strlen(s._pStr)+1])
{
strcpy(_pStr, s._pStr);
}
String& operator=(const String& s)
{
if (this != &s)
{
char* pTemp = new char[strlen(s._pStr) + 1];
strcpy(pTemp, s._pStr);
delete[]_pStr;
_pStr = pTemp;
}
return *this;
}
~String()
{
delete[]_pStr;
_pStr = NULL;
}
private:
char* _pStr;
};
与浅拷贝原始函数相比,仅拷贝构造函数与赋值运算符重载需要修改
再运用swap函数对其改造
String(const String &s)
:_pStr(NULL)
{
String str(s._pStr);
swap(_pStr, str._pStr);
}
修改后的拷贝构造,需注意要将_pStr赋空指针,否则会造成销毁野指针而出错;
String& operator=(String s)
{
swap(_pStr, s._pStr);
return *this;
}
赋值运算符重载中,利用拷贝构造一个s,出函数时会对其进行析构,函数中利用s对this中的指针进行交换赋值。