【面试题】 类的赋值运算符函数
题目:CMyString类型声明如下,请为其添加“赋值运算符”函数。
class CMyString { public: CMyString(char* pData = NULL); CMyString(const CMyString& str); ~CMyString(void); private: char* m_pData; };
对于此类问题,面试官通常关注以下几点:
1. 返回值类型是否为引用,函数结束时是否“return *this;”。只有这样,才能允许CMyString类型对象的连续赋值。
2. 参数类型是否为常量引用。如果传入参数非引用而是实例,则从形参到实参会调用一次拷贝构造函数,造成性能损耗。赋值运算符函数内不会改变参数状态,因此应该为引用参数加上const关键字。
3. 是否释放m_pData之前所指向内存,否则将造成内存泄露。
4. 是否判断“传入参数与*this是否相等”,如果相等则直接返回。如果不作判断,且*this和传入参数是同一实例,那么一旦释放自身内存,则传入参数的内存也同时被释放,就再也找不到需要赋值的字符串内容了。
完整考虑上述4点,可写出如下代码:
CMyString& CMyString::operator = (const CMyString& str) { if(this == &str) return *this; delete [] m_pData; m_pData = NULL; m_pData = new char[strlen(str.m_pData) + 1]; strcpy(m_pData, str.m_pData); return *this; }
上面函数中,先delete释放已有内容,这样做其实还存在隐患。如果在new时抛出异常,CMyString的实例将不再保持有效状态,违背了异常安全(Exception Safety)原则。可以修改如下:
CMyString& CMyString::operator = (const CMyString& str) { if(this == &str) return *this; char * auxiliary = m_pData; m_pData = new char[strlen(str.m_pData) + 1]; strcpy(m_pData, str.m_pData); delete [] auxiliary; auxiliary = NULL; return *this; }
还有一种实现方式如下:
CMyString& CMyString::operator =(const CMyString & str) { if (this != &str) { CMyString strTemp(str); char * auxiliary = strTemp.m_pData; strTemp.m_pData = m_pData; m_pData = auxiliary; } return *this; }
先创建一个临时实例strTemp,然后把strTemp.m_pData与实例自身m_pData交换。strTemp是if语句内的局部变量,会自动调用析构函数销毁,strTemp.m_pData所指向的内存就是实例之前m_pData的内存。在执行创建临时变量strTemp的构造函数时,如果new操作抛出异常,这时还没有修改实例状态,因此实例状态还是有效的,也就保证了异常安全。
【学习资料】 《剑指offer 名企面试官精讲典型面试题》