赋值运算符函数
题目
如下为类型 CMyString 的声明,请为该类型添加赋值运算符函数
class CMyString
{
public:
CMyString(char *pData = nullptr);
CMyString(const CMyString& str);
~CMyString(void);
private:
char *m_pData;
};
此题需注意如下几点
是否把返回值的类型声明为该类型的引用,并在函数结束前返回实例自身的引用(
*this
)。- 只有返回一个引用,才可以允许连续赋值
str1=str2=str3;
- 只有返回一个引用,才可以允许连续赋值
是否把传入的参数声明为常量引用(
const
)- 如果传入的参数是实例而不是引用,则从形参到实参复制一次构造函数;将参数声明为引用可以避免这种不必要的消耗,并且在赋值运算符函数内不会改变传入实例的状态
是否释放自身已有的内存
- 在分配新内存之前未释放已有的空间,会出现内存泄漏
判断传入的参数是不是当前的实例(
*this
)- 如果是则直接返回当前实例,如果不是再进行赋值操作,如果不进行判断,释放掉了自身的内存,传入的参数的内存也同时被释放,就找不到被赋值的内容了
经典解法 —— 初级
CMyString& CMyString::operator = (const CMyString& str)
{
if(this == &str)
{
return *this;
}
delete []m_pData;
m_pData = nullptr;
m_pData = new char[srtlen(str.m_pData) + 1];
strcpy(m_pData, str.m_pData);
return *this;
}
这样的程序存在一些问题
在分配内存前用 delete
释放了 m_pData
的内存,如果此时内存不足导致 new char[]
抛出异常,m_pData
将会是一个空指针,很容易导致程序崩溃
- 也即是说,一旦在赋值运算符函数内部抛出一个异常,
CMyString
的实例将不在保持有效的状态,违背了异常安全性原则
考虑异常安全性的解法 —— 高级
可以在 delete
前先用 new
分配内存,能保证在分配内存失败时 CMyString
的实例也不会被修改
还有一种更好的办法
- 创建一个临时的实例,再交换临时实例和原来的实例
CMyString& CMyString::operator = (const CMyString& str)
{
if(this != &str)
{
CMyString str_temp(str);
char *p_temp = str_temp.m_pData;
str_temp.m_pData = m_pData;
m_pData = p_temp;
}
return *this;
}
这里创建的 str_temp
是一个局部变量,当程序运行到作用域之外时,会自动调用析构函数,把 str_temp.m_pData
所指向的内存释放掉
而 str_temp.m_pData
所指向的内存就是实例之前 m_pData
所指向的内存,相当于自动调用析构函数释放实例的内存
测试用例
把一个
CMyString
的实例赋值给另外一个实例把一个
CMyString
的实例赋值给它自己连续赋值
总结
本题考点
对
C++
基本语法的理解,如运算符函数、常量引用等对内存泄漏的理解
对代码异常安全性的理解