Skip to main content

赋值运算符函数

题目

如下为类型 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++ 基本语法的理解,如运算符函数、常量引用等

  • 对内存泄漏的理解

  • 对代码异常安全性的理解