异常安全的赋值运算符重载函数

题目:类CMyString的声明如下:

class CMyString
{
public:
      CMyString(char* pData = NULL);
      CMyString(const CMyString& str);
      ~CMyString(void);
      CMyString& operator = (const CMyString& str);

private:
      char* m_pData;
};

  请实现其赋值运算符的重载函数,要求异常安全,即当对一个对象进行赋值时发生异常,对象的状态不能改变。

分析:首先我们来看一般C++教科书上给出的赋值运算符的重载函数:

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;
}

  我们知道,在分配内存时有可能发生异常。当执行语句

new char[strlen(str.m_pData) + 1]发生异常时,程序将从该赋值运算符的重载函数退出不再执行。注意到这个时候语句delete []m_pData已经执行了。也就是说赋值操作没有完成,但原来对象的状态已经改变。也就是说不满足题目的异常安全的要求。

为了满足异常安全这个要求,一个简单的办法是掉换newdelete的顺序。先把内存new出来用一个临时指针保存起来,只有这个语句正常执行完成之后再执行delete。这样就能够保证异常安全了。

下面给出的是一个更加优雅的实现方案:

CMyString& CMyString::operator =(const CMyString &str)
{
      if(this != &str)
      {
            CMyString strTemp(str);

            char* pTemp = strTemp.m_pData;
            strTemp.m_pData = m_pData;
            m_pData = pTemp;
      }

      return *this;
}

  

  该方案通过调用构造拷贝函数创建一个临时对象来分配内存。此时即使发生异常,对原来对象的状态没有影响。交换临时对象和需要赋值的对象的字符串指针之后,由于临时对象的生命周期结束,自动调用其析构函数释放需赋值对象的原来的字符串空间。整个函数不需要显式用到newdelete,内存的分配和释放都自动完成,因此代码显得比较优雅。

在C++中,**赋值运算符重载(`operator=`)**用于自定义类对象的赋值行为。它必须返回当前对象的引用(`*this`),以支持连续赋值(如`a = b = c`),并正确处理自赋值(如`a = a`)以避免资源泄漏或重复释放。以下是详细说明和示例: --- ### **1. 赋值运算符重载的基本语法** ```cpp class MyClass { public: // 赋值运算符重载 MyClass& operator=(const MyClass& other) { if (this != &other) { // 处理自赋值 // 1. 释放当前对象的资源(如有) // 2. 深拷贝other的资源到当前对象 } return *this; // 返回当前对象的引用 } // 移动赋值运算符(C++11起) MyClass& operator=(MyClass&& other) noexcept { if (this != &other) { // 1. 释放当前对象的资源 // 2. 移动other的资源到当前对象 // 3. 将other置于有效但未定义的状态 } return *this; } }; ``` --- ### **2. 关键注意事项** #### **(1)返回当前对象的引用** - 必须返回`*this`(即当前对象的引用),以支持链式赋值: ```cpp MyClass a, b, c; a = b = c; // 等价于 a.operator=(b.operator=(c)) ``` #### **(2)处理自赋值** - 自赋值(如`a = a`)可能导致资源重复释放或未定义行为,需显式检查: ```cpp if (this != &other) { ... } ``` #### **(3)深拷贝与资源管理** - 如果类管理动态资源(如指针、文件句柄等),需在赋值时深拷贝资源,避免浅拷贝导致的双重释放: ```cpp class String { char* data; size_t size; public: String& operator=(const String& other) { if (this != &other) { delete[] data; // 释放旧资源 size = other.size; data = new char[size]; // 分配新资源 std::copy(other.data, other.data + size, data); // 深拷贝 } return *this; } }; ``` #### **(4)移动赋值运算符(C++11)** - 移动赋值通过“窃取”右值对象的资源来避免不必要的拷贝,提升性能: ```cpp String& operator=(String&& other) noexcept { if (this != &other) { delete[] data; // 释放当前资源 data = other.data; // 接管右值资源 size = other.size; other.data = nullptr; // 置空右值,避免重复释放 } return *this; } ``` --- ### **3. 完整示例** #### **(1)动态数组类赋值运算符** ```cpp #include <iostream> #include <algorithm> class IntArray { int* data; size_t size; public: // 构造函数 IntArray(size_t n) : size(n), data(new int[n]) {} // 析构函数 ~IntArray() { delete[] data; } // 拷贝赋值运算符 IntArray& operator=(const IntArray& other) { if (this != &other) { delete[] data; // 释放旧资源 size = other.size; data = new int[size]; // 分配新资源 std::copy(other.data, other.data + size, data); // 深拷贝 } return *this; } // 移动赋值运算符 IntArray& operator=(IntArray&& other) noexcept { if (this != &other) { delete[] data; // 释放当前资源 data = other.data; // 接管资源 size = other.size; other.data = nullptr; // 置空右值 other.size = 0; } return *this; } // 打印数组 void print() const { for (size_t i = 0; i < size; ++i) std::cout << data[i] << " "; std::cout << "\n"; } }; int main() { IntArray a(3); a = {1, 2, 3}; // 初始化(假设有初始化列表构造函数) IntArray b(3); b = a; // 调用拷贝赋值 IntArray c = std::move(a); // 调用移动赋值 b.print(); // 输出: 1 2 3 return 0; } ``` #### **(2)拷贝并交换(Copy-and-Swap)惯用法** - 通过`std::swap`简化赋值运算符的实现,同时处理自赋值和异常安全: ```cpp #include <utility> // for std::swap class String { char* data; size_t size; public: // 拷贝赋值运算符(使用拷贝并交换) String& operator=(String other) { // 注意:传值以利用拷贝构造函数 std::swap(data, other.data); std::swap(size, other.size); return *this; } // 移动赋值运算符(可省略,由拷贝赋值+传值优化实现) }; ``` --- ### **4. 特殊场景** #### **(1)继承中的赋值运算符** - 派生类的赋值运算符需显式调用基类的赋值运算符: ```cpp class Base { public: Base& operator=(const Base& other) { ... } }; class Derived : public Base { public: Derived& operator=(const Derived& other) { Base::operator=(other); // 调用基类赋值 // 处理派生类成员 return *this; } }; ``` #### **(2)禁止赋值** - 若需禁止赋值操作,可将`operator=`声明为`delete`: ```cpp class NonAssignable { public: NonAssignable& operator=(const NonAssignable&) = delete; }; ``` --- ### **5. 总结** | **要点** | **说明** | |------------------------|--------------------------------------------------------------------------| | **返回值** | 必须返回`*this`的引用(支持链式赋值) | | **自赋值处理** | 显式检查`this != &other`,避免资源重复释放 | | **深拷贝** | 管理动态资源时需深拷贝,避免浅拷贝问题 | | **移动语义(C++11)** | 通过移动赋值提升性能,接管右值资源 | | **异常安全** | 使用拷贝并交换(Copy-and-Swap)惯用法简化实现并保证异常安全 | **核心原则**: 赋值运算符需确保对象在赋值后与源对象逻辑等价,同时正确管理资源(避免泄漏或重复释放),并支持自赋值和链式赋值。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值