【拷贝赋值运算符】注意事项
拷贝赋值运算符
点击查看代码
// 拷贝赋值运算符
SimpleC& operator=(const SimpleC& other)
{
std::cout << "赋值构造开始\n";
if (this == &other)
{
std::cout << "赋值构造结束\n";
return *this;
}
delete _ptr;
_ptr = new int(*other._ptr);
std::cout << "赋值构造结束\n";
return *this;
}
//这是最简单的赋值构造,请帮忙检查有什么问题
❗存在 2 个严重隐患
隐患 1:
new可能抛出异常,会造成本对象内部指针变成悬空 / 泄漏
隐患 2:
没有考虑 other._ptr == nullptr 的情况
如果other是空的(other._ptr = nullptr),执行 *other._ptr → 解引用空指针,直接崩溃 UB。
原因解释说明
new 抛异常概率
业务上正常跑,内存充足的时候几乎碰不到;但是做底层库(例如手写智能指针属于底层基础组件),不能假设 “new 永远成功”。
1、什么时候会抛 std::bad_alloc
进程内存耗尽,操作系统不给分配新堆内存,new 抛出异常。
普通桌面开发,机器内存很大,日常测试几乎遇不到;
但是:嵌入式、长时间运行服务、大批量分配内存、内存泄漏把内存吃光,就会触发。
关键不是 “概率高不高”,而是库的契约
如果代码是作为基础组件库的开发,此类情况属于极其严重BUG,而且难以排查复现
2、处理方法:两种主流方案
方案 A:先拷贝新资源,再替换(异常安全)
点击查看代码
SimpleC& operator=(const SimpleC& other)
{
std::cout << "拷贝赋值开始\n";
if (this == &other)
{
std::cout << "拷贝赋值结束\n";
return *this;
}
// 先处理源为空的情况
int* new_ptr = nullptr;
if (other._ptr != nullptr)
{
new_ptr = new int(*other._ptr); //分配新内存,如果抛异常,this对象完好无损
}
delete _ptr; //分配成功,才释放旧资源
_ptr = new_ptr;
std::cout << "拷贝赋值结束\n";
return *this;
}
方案 B:拷贝交换惯用法(copy‑and‑swap,更优雅强异常安全)
点击查看代码
//先实现拷贝构造,再swap
SimpleC(const SimpleC& other)
{
if(other._ptr != nullptr)
_ptr = new int(*other._ptr);
else
_ptr = nullptr;
}
void swap(SimpleC& rhs) noexcept
{
std::swap(_ptr, rhs._ptr);
}
SimpleC& operator=(const SimpleC& other)
{
SimpleC temp(other); //调用拷贝构造,失败则抛异常,*this不受影响
swap(temp);
return *this;
}
浙公网安备 33010602011771号