C++ 拷贝赋值、swap 与 noexcept 深度解析:高效实现 operator=
在 C++ 自定义类中,常常需要实现拷贝构造、赋值运算符、析构函数,其中,operator=
(赋值运算符)处理不当,容易导致内存泄露、浅拷贝错误或性能浪费。
本文通过一个简化的动态数组类 MyArray
,一步步分析如何编写安全、高效、现代化的赋值运算符,并解释相关关键词如 std::copy
、值传递 vs 引用传递、swap
技巧和 noexcept
优化等。
一、传统的赋值运算符实现方式
MyArray<T>& MyArray<T>::operator=(const MyArray<T>& arr) {if (this == &arr) return *this; // 防止自赋值if (this->pAddress != nullptr) {delete[] this->pAddress;this->pAddress = nullptr;}this->m_Capacity = arr.m_Capacity;this->m_Size = arr.m_Size;this->pAddress = new T[m_Capacity];std::copy(arr.pAddress, arr.pAddress + m_Size, this->pAddress);return *this;
}
优点:
支持自赋值检测
深拷贝逻辑安全
缺点:
重复代码较多
写法不够现代
容易出错,性能不高
现代推荐写法:Copy-and-Swap 技巧
MyArray& operator=(MyArray other) {swap(*this, other); // 利用参数副本直接交换std::cout << "拷贝/赋值 operator= 调用" << std::endl;return *this;
}
搭配一个全局友元函数 swap
:
friend void swap(MyArray& a, MyArray& b) noexcept {using std::swap;swap(a.pAddress, b.pAddress);swap(a.m_Capacity, b.m_Capacity);swap(a.m_Size, b.m_Size);
}
优点:
安全:借助值传递(拷贝构造),出错不影响原对象
简洁:释放、分配资源交给 swap 管
可与移动构造结合,效率更高
二、为什么值传递(而不是引用)?
MyArray& operator=(MyArray other); // 传值
好处:
自然实现自我保护,不需要显式处理自赋值 if (this == &other)
结合移动构造,右值传参效率更高:
MyArray arr2 = std::move(arr1); 会触发移动构造 + swap,避免深拷贝
语义简洁,无需再判断是否释放旧资源,一次 swap 解决所有权交接
但也要注意:
如果传入的是左值,确实会生成一份副本,可能引发性能浪费(尤其数组很大时)。
std::copy
与 for 循环对比
std::copy(arr.pAddress, arr.pAddress + m_Size, this->pAddress);
等价于:
for (int i = 0; i < m_Size; ++i) {this->pAddress[i] = arr.pAddress[i];
}
优点:
语义清晰
简洁,易读
编译器可对其优化(比如使用 SIMD 指令)
noexcept
的作用
friend void swap(MyArray& a, MyArray& b) noexcept
noexcept
说明该函数不会抛出异常。
优点:
提高性能
编译器可跳过异常处理路径STL 容器兼容性更好
如std::vector
只有在你的移动构造/赋值是noexcept
才会使用它们更强的异常安全保证
防止程序在意外抛异常时崩溃
三、完整代码示意
template<class T>
class MyArray
{
public://如果你不传入参数,默认就使用 capacity = 0MyArray(std::size_t capacity = 0):pAddress(capacity ? new T[capacity] : nullptr),m_Capacity(capacity),m_Size(0) {std::cout << "有参构造函数调用" << std::endl;}//拷贝构造MyArray(const MyArray& arr):pAddress(arr.m_Capacity ? new T[arr.m_Capacity] : nullptr),m_Capacity(arr.m_Capacity),m_Size(arr.m_Size){std::cout << "拷贝构造调用" << std::endl;std::copy(arr.pAddress, arr.pAddress + arr.m_Size, pAddress);}//移动构造MyArray(MyArray&& arr)noexcept:pAddress(arr.pAddress),m_Capacity(arr.m_Capacity),m_Size(arr.m_Size){std::cout << "移动构造函数调用" << std::endl;arr.pAddress = nullptr;arr.m_Capacity = 0;arr.m_Size = 0;}//operator=MyArray& operator=(MyArray arr){swap(*this, arr);std::cout << "operator= 调用" << std::endl;return *this;}friend void swap(MyArray& a, MyArray& b) noexcept{using std::swap;swap(a.pAddress, b.pAddress);swap(a.m_Capacity, b.m_Capacity);swap(a.m_Size, b.m_Size);}//析构函数~MyArray(){std::cout << "析构函数的调用" << std::endl;delete[]this->pAddress;this->pAddress = nullptr;}
private:T* pAddress;size_t m_Capacity;size_t m_Size;
};