面试官扔给你一个类,让你去实现赋值运算符重载。
class String
{
public:
String(const char* p = "")
{
if(p == nullptr)
{
p = "";
}
m_pdata = new char[strlen(p) + 1];
strcpy(m_pdata,p);
}
String(const String & str)
{
m_pdata = new char[strlen(str.m_pdata) + 1];
strcpy(m_pdata,str.m_pdata);
}
public:
~String()
{
if(m_pdata != nullptr)
{
delete[] m_pdata;
m_pdata = NULL;
}
}
public:
friend ostream& operator << (ostream & out,const String & s)
{
out << s.m_pdata;
return out;
}
private:
char * m_pdata;
};
如果是我,我会想:重载赋值运算符的返回值:
void operator = (const String & s);
我为什么不能是void? 为什么源码内部实现返回值是 String &
我们来看,如果这条语句
s1 = s2 = s3;
返回值是void就完全行不通了。
考虑了返回值,我会这样写:
String& operator= (const String & s)
{
if(this == &s)
{
return *this;
}
m_pdata = new char[strlen(s.m_pdata) + 1];
strcpy(m_pdata,s.m_pdata);
return *this;
}
写完之后甚至感觉自我良好,但是,我们赋值的时候原先的值一定是不存在的吗?
例如:
s1 = s2
s1一定是没有自己空间的吗?
这个代码有严重的内存泄露问题。
如果s1原先有自己空间,这样写就会造成原先空间没有得到释放。
我们继续来改正:
String& operator= (const String & s)
{
if(this == &s)
{
return *this;
}
if(m_pdata != NULL)
delete[] m_pdata;
m_pdata = NULL;
m_pdata = new char[strlen(s.m_pdata) + 1];
strcpy(m_pdata,s.m_pdata);
return *this;
}
这段代码感觉上是我又行了,但是实质上也会存在一个问题。
如果new失败了,这样就会造成异常返回,但是我们
delete[] m_data. m_data = NULL
就会严重影响后续操作。
我们怎么规避呢?
我们看源码可以发现,源码中使用到了swap的思想。
我们来看代码:
String & operator = (const String& s)
{
if(this == &s)
{
return *this;
}
String TempStr(s);
char* ptemp = TempStr.m_pdata;
TempStr.m_pdata = m_pdata;
m_pdata = ptemp;
return *this;
}
这就是我们所看到的较为完整的代码。
我们来看为什么这个代码能够实现很多一场规避呢?
首先使用到了临时变量`TempStr
在函数结束的时候会自动将这个类销毁掉,我们不用担心内存问题。
我们将ptemp 和 m_pdata交换,是将这两个指针管理的内存空间进行交换,我们不用担心释放,因为我们的拷贝构造函数会替我们考虑,也就减少了代码的重复度,同时我们最关心的问题: NEW失败后会怎么办?
我们这个代码new失败异常返回后并不会更改p_mdata,使得代码安全性上有了进一步提升。也就是我们最终想要的代码。
代码地址:
https://gitee.com/dragon-web/sword-finger-offer/tree/
master/day_1