<h2>题目:如下为类型CMyString的声明,请为该类添加赋值运算符函数。</h2>
<pre>
class CMyString
{
public:
CMyString(char* pData = NULL);
CMyString(const CMyString& str);
~CMyString(void);
private:
char* m_pData;
};</pre>
<h2>分析</h2>
我们知道,C++中类的6个默认的函数分别是构造,拷贝构造,析构,赋值运算符的重载,取址运算符的重载和它的常量版本。其中前4个在面试中最常问到。比如在继承/组合类中构造和析构函数的调用顺序,派生类中析构函数是否应该设置为virtual,拷贝构造在何时被调用以及注意事项等等。
但要注意的是,对于拷贝构造函数和赋值运算符的重载函数,C++默认实现的是按“位”拷贝的方式,也就是将该对象的内存原封不动地挪到新对象的内存中。所以对于成员变量中含有指针的类,我们一定需要自己实现这两个方法(除非你就打算以默认的方式实现),否则在析构时会将同一块空间释放两次导致崩溃。这就是“深浅拷贝”问题。
解决方案有两种:重新分配内存并拷贝或实现引用计数。很明显,对于CMyString类我们需要采用前者。
<h2>答案代码如下:</h2>
<h3>version 1:最普通的写法,没有考虑到自赋值的情况和异常安全性</h3>
<pre>
CMyString& CMyString::operator=(const CMyString& str)
{
delete []m_pData;
m_pData=NULL;
size_t len=strlen(str.m_pData);
m_pData=new char[len+1];
memcpy(m_pData,str.m_pData,len+1);
return *this;
}
</pre>
<h3>version 2:比上个版本好一点,检查了自赋值的情况</br>但如果new操作符失败而抛出异常,会导致自己原来的空间被释放</h3>
<pre>
CMyString& CMyString::operator=(const CMyString& str)
{
if(&str!=this)
{
delete []m_pData;
m_pData=NULL;
size_t len=strlen(str.m_pData);
m_pData=new char[len+1];
memcpy(m_pData,str.m_pData,len+1);
}
return *this;
}
</pre>
<h3>version 3:交换当前指针和一个临时对象,该对象析构时会释放掉当前的指针</br>由于swap是原子操作,所以有很好的异常安全性,推荐这种写法</h3>
<pre>
CMyString& CMyString::operator=(const CMyString& str)
{
CMyString tmp(str.m_pData);
std::swap(tmp.m_pData,m_pData);
return *this;
}
</pre>
<h3>version 4:和版本3原理一样,但需要修改函数声明</h3>
<pre>
CMyString& CMyString::operator=(CMyString str)
{
swap(str.m_pData,m_pData);
return *this;
}
</pre>
<h3>以上</h3>
如果你有任何想法或是可以改进的地方,欢迎和我交流!
完整代码及测试用例在github上:<a href="https://github.com/SmartBrave/Sword-to-Offer/blob/master/01_AssignmentOperator/main.cpp" target="_blank">点我前往</a>
<pre>
class CMyString
{
public:
CMyString(char* pData = NULL);
CMyString(const CMyString& str);
~CMyString(void);
private:
char* m_pData;
};</pre>
<h2>分析</h2>
我们知道,C++中类的6个默认的函数分别是构造,拷贝构造,析构,赋值运算符的重载,取址运算符的重载和它的常量版本。其中前4个在面试中最常问到。比如在继承/组合类中构造和析构函数的调用顺序,派生类中析构函数是否应该设置为virtual,拷贝构造在何时被调用以及注意事项等等。
但要注意的是,对于拷贝构造函数和赋值运算符的重载函数,C++默认实现的是按“位”拷贝的方式,也就是将该对象的内存原封不动地挪到新对象的内存中。所以对于成员变量中含有指针的类,我们一定需要自己实现这两个方法(除非你就打算以默认的方式实现),否则在析构时会将同一块空间释放两次导致崩溃。这就是“深浅拷贝”问题。
解决方案有两种:重新分配内存并拷贝或实现引用计数。很明显,对于CMyString类我们需要采用前者。
<h2>答案代码如下:</h2>
<h3>version 1:最普通的写法,没有考虑到自赋值的情况和异常安全性</h3>
<pre>
CMyString& CMyString::operator=(const CMyString& str)
{
delete []m_pData;
m_pData=NULL;
size_t len=strlen(str.m_pData);
m_pData=new char[len+1];
memcpy(m_pData,str.m_pData,len+1);
return *this;
}
</pre>
<h3>version 2:比上个版本好一点,检查了自赋值的情况</br>但如果new操作符失败而抛出异常,会导致自己原来的空间被释放</h3>
<pre>
CMyString& CMyString::operator=(const CMyString& str)
{
if(&str!=this)
{
delete []m_pData;
m_pData=NULL;
size_t len=strlen(str.m_pData);
m_pData=new char[len+1];
memcpy(m_pData,str.m_pData,len+1);
}
return *this;
}
</pre>
<h3>version 3:交换当前指针和一个临时对象,该对象析构时会释放掉当前的指针</br>由于swap是原子操作,所以有很好的异常安全性,推荐这种写法</h3>
<pre>
CMyString& CMyString::operator=(const CMyString& str)
{
CMyString tmp(str.m_pData);
std::swap(tmp.m_pData,m_pData);
return *this;
}
</pre>
<h3>version 4:和版本3原理一样,但需要修改函数声明</h3>
<pre>
CMyString& CMyString::operator=(CMyString str)
{
swap(str.m_pData,m_pData);
return *this;
}
</pre>
<h3>以上</h3>
如果你有任何想法或是可以改进的地方,欢迎和我交流!
完整代码及测试用例在github上:<a href="https://github.com/SmartBrave/Sword-to-Offer/blob/master/01_AssignmentOperator/main.cpp" target="_blank">点我前往</a>