C++ 赋值运算符重载:深拷贝 vs 浅拷贝的生死线!

🔄 C++ 赋值运算符重载:深拷贝 vs 浅拷贝的生死线!

大家好!今天我们来聊一个 C++ 中极易被忽视、却可能引发严重 bug 的知识点------赋值运算符 operator= 的重载。

你可能写过 a = b,但当你的类中包含指向堆内存的指针时,这个看似简单的等号,就可能让你的程序崩溃、内存泄漏,甚至"神秘地"修改不该改的数据!

别慌,今天我们就用一段经典示例,彻底搞懂 为什么需要重载赋值运算符 ,以及 如何正确实现深拷贝。


🧠 编译器默认给你的 4 个函数

在 C++ 中,即使你什么都没写,编译器也会悄悄为你的类生成以下 4 个函数:

  1. 默认构造函数(无参,空实现)

  2. 默认析构函数(无参,空实现)

  3. 默认拷贝构造函数(逐成员值拷贝)

  4. **默认赋值运算符 operator=**(也是逐成员值拷贝)

⚠️ 问题来了:"值拷贝"对指针来说,就是"浅拷贝"!


💥 浅拷贝的灾难:多个对象共用一块堆内存

来看你写的 Person 类(代码原样保留,未作任何修改):

cpp 复制代码
class Person
{
public:
    Person(int age)
    {
        // 将年龄数据开辟到堆区
        m_Age = new int(age);
    }

    // 重载赋值运算符 
    Person& operator=(Person &p)
    {
        if (m_Age != NULL)
        {
            delete m_Age;
            m_Age = NULL;
        }
        // 编译器提供的代码是浅拷贝
        // m_Age = p.m_Age;

        // 提供深拷贝 解决浅拷贝的问题
        m_Age = new int(*p.m_Age);

        // 返回自身
        return *this;
    }

    ~Person()
    {
        if (m_Age != NULL)
        {
            delete m_Age;
            m_Age = NULL;
        }
    }

    // 年龄的指针
    int *m_Age;
};

如果不重载 operator=,会发生什么?

假设使用默认赋值:

cpp 复制代码
p2 = p1; // 默认:m_Age = p1.m_Age (浅拷贝!)

结果:

  • p1.m_Age 和 p2.m_Age 指向同一块堆内存

  • 当 p1 或 p2 析构时,delete 这块内存

  • 另一个对象再访问或析构 → 野指针 / 重复释放 → 程序崩溃!

这就是典型的浅拷贝陷阱。


✅ 正确做法:手动实现深拷贝

你的重载版本完美解决了这个问题:

cpp 复制代码
Person& operator=(Person &p)
{
    if (m_Age != NULL)
    {
        delete m_Age;
        m_Age = NULL;
    }
    m_Age = new int(*p.m_Age); // 深拷贝:新开内存,复制值
    return *this;
}

关键步骤:

  1. 先释放自身原有堆内存(防止内存泄漏)

  2. 从源对象的堆数据中读取值,重新 new 一块新内存

  3. 返回 *this 的引用 ,支持链式赋值(如 p3 = p2 = p1)


🧪 测试效果

cpp 复制代码
void test01()
{
    Person p1(18);
    Person p2(20);
    Person p3(30);

    p3 = p2 = p1; // 链式赋值

    cout << "p1的年龄为:" << *p1.m_Age << endl;
    cout << "p2的年龄为:" << *p2.m_Age << endl;
    cout << "p3的年龄为:" << *p3.m_Age << endl;
}

输出:

cpp 复制代码
p1的年龄为:18
p2的年龄为:18
p3的年龄为:18

✅ 三个对象各自拥有独立的堆内存,互不影响!

✅ 支持 p3 = p2 = p1 链式赋值(因为返回了 *this 引用)!


📌 黄金法则:三/五法则(Rule of Three/Five)

如果你的类中:

  • 使用了 动态内存 (如 new)

  • 或管理了 其他资源(文件句柄、socket 等)

那么你很可能需要同时自定义:

  • 析构函数

  • 拷贝构造函数

  • 赋值运算符

这就是著名的 "三法则"(C++11 后扩展为"五法则",加上移动构造和移动赋值)

否则,默认的浅拷贝会让你陷入万劫不复的调试深渊!


✅ 总结

  • 编译器自动生成的 operator= 是浅拷贝,对指针极其危险。

  • 当类中有堆区指针时,必须重载赋值运算符 ,实现深拷贝。

  • 记得:先释放旧资源,再分配新资源,最后返回 *this。

  • 支持链式赋值的关键:返回引用!


如果你觉得这篇推文帮你避开了一个大坑,欢迎点赞、收藏、转发!

也欢迎留言:"你在项目中遇到过浅拷贝导致的 bug 吗?"


相关推荐
拆房老料4 小时前
ONLYOFFICE也能像Microsoft Word和WPS一样分别设置中西文字体
前端·html·word·开源软件·wps
汉克老师4 小时前
CSP-S 2026 初赛试题解析(第一部分:选择题(8-15))精讲
c++·csp-s·小学生·学c++编程
Solis程序员4 小时前
Stripe 幂等表:把「不确定的重试」变成「查表返回」
前端·安全·bootstrap·php·agent
_wyt0015 小时前
洛谷 P10112 [GESP202312 八级] 奖品分配 题解
c++·数学·gesp·八级
李纲明5 小时前
WordPress 站点变慢:按 TTFB → 缓存 → 查询 → 前端 分层排查(含配置注意)
前端·缓存·性能优化·wordpress·后端开发
程序人生8885 小时前
PDF 转 Word 版式错乱、表格丢失?DocConverter Web 格式互转引擎的保真实践
前端·人工智能·opencv·机器学习·pdf·word
朝朝辞暮i5 小时前
C++ 第 33 课:事件循环——spin() 到底在干什么
开发语言·c++·算法
殷色玫瑰5 小时前
AVL树:从平衡因子到四种旋转的完整实现
c语言·数据结构·c++·算法·visualstudio
进化中的刀盾兽6 小时前
从零实现 C++ AI 大模型接入 SDK(九):LLMManager 统一模型管理与请求路由
c++
会周易的程序员6 小时前
STVM OSAL 层架构设计文档
c++·物联网·嵌入式·虚拟机·iot·软plc·iec61131