【拷贝赋值运算符】注意事项

拷贝赋值运算符

点击查看代码
// 拷贝赋值运算符
        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;
        }
		//这是最简单的赋值构造,请帮忙检查有什么问题
✅做得好的地方 做了自赋值判断 this == &other,防止 obj = obj 灾难; 不是浅拷贝,做深拷贝:new int(*other._ptr),重新分配内存复制值。 不会出现两个对象共用同一块内存、双重释放的问题。

❗存在 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;
}
posted @ 2026-08-12 22:10  mnst  阅读(7)  评论(0)    收藏  举报