C++:深拷贝与浅拷贝

拷贝构造函数 (Copy Constructor) 单独拿出来,放在显微镜下看。

这部分逻辑之所以难,是因为它同时也涉及到了内存模型。要彻底理解深拷贝,你脑海里必须有一个动态的画面。

我用**"复印机与仓库"**的类比,配合一行行的代码拆解,带你彻底跑通这个逻辑。


一、 核心逻辑:从"配钥匙"到"建新房"

假设 Lidar 对象就是你家。

  • rpm(转速)是你家墙上的挂钟。

  • data(指针)是你手里的一把钥匙 ,通向一个外部仓库(堆内存),里面放着雷达数据。

1. 浅拷贝 (Shallow Copy) ------ 仅仅是配了一把钥匙

编译器默认干的事,就是简单的"配钥匙"。

  • 它把挂钟(rpm)复制了一个给你朋友。

  • 它把钥匙(data)复制了一把给你朋友。

  • 后果 :你和你朋友手里拿着一样的钥匙,打开的是同一个仓库。

    • 你朋友打扫仓库(delete),你的仓库也没了。

    • 你再去仓库拿东西,发现空了或者塌了(Crash)。

2. 深拷贝 (Deep Copy) ------ 建一个一模一样的仓库

深拷贝构造函数的逻辑是:

  • 给我朋友也建一个新的仓库 (new int[])。

  • 把我仓库里的东西,一件一件搬运 到他的新仓库里(for 循环赋值)。

  • 最后把新仓库的钥匙给朋友。


二、 代码解剖台:逐行解析

我们把刚才那段代码拆开,每一行都有它的深意。

C++

复制代码
// 语法解读:
// 1. Lidar: 函数名和类名相同,说明是构造函数。
// 2. const Lidar& other: 
//    - const: 我保证在拷贝过程中,不会修改原来的那个对象(other)。
//    - &: 必须用引用!(后面解释为什么)
Lidar(const Lidar& other) {

    // --- 第一步:基本数据的"浅拷贝" ---
    // 像 int, bool 这种死板的数据,直接赋值就行。
    // 这一步相当于复制了"挂钟"。
    this->is_active = other.is_active;
    this->rpm = other.rpm;
    this->m_size = other.m_size; // 关键:必须知道仓库有多大

    // --- 第二步:申请"新地盘" (最关键的一步) ---
    // 如果没有这一行,data 就是野指针,或者只是复制了别人的地址。
    // new int[m_size] 会在堆内存里开辟一块【全新的、独立的】空间。
    // this->data 拿到了这个新空间的地址。
    this->data = new int[m_size];

    // --- 第三步:搬运数据 ---
    // 此时,新仓库是空的(或者里面是垃圾值)。
    // 我们需要把 old(other) 仓库里的数据,一个一个填进来。
    for(int i = 0; i < m_size; i++) {
        // 左边是新仓库,右边是老仓库
        this->data[i] = other.data[i];
    }
    
    // 此时,两个对象完全独立了。
}

三、 灵魂拷问:为什么要用引用 &?

你可能会问:"我在普通函数传参时,偶尔不用 & 也没事,为什么拷贝构造函数里的参数 (const Lidar& other) 必须加 &?"

这是一个经典的死循环陷阱。

假设你写成这样(去掉了 &):

C++

复制代码
Lidar(const Lidar other) { ... } // ❌ 错误写法

推演过程:

  1. main 函数调用 Lidar lidar2 = lidar1;。

  2. 系统准备调用拷贝构造函数。

  3. 看到参数是 Lidar other(传值)。

  4. 根据 C++ 规则,传值参数需要被拷贝。

  5. 为了拷贝 lidar1 给 other,系统需要再次调用拷贝构造函数。

  6. 再次调用时,又要传参,又要拷贝......

  7. 无限套娃 (Infinite Recursion) -> 栈溢出 (Stack Overflow) -> 程序崩溃。

所以,拷贝构造函数的参数必须是引用,这打断了无限拷贝的链条。


四、 什么时候需要深拷贝?

并不是所有类都需要这么麻烦。只有当你的类满足以下**"有产阶级"**特征时才需要:

  1. 有指针成员 (int* data, char* buffer)。

  2. 且 这个指针指向的是动态分配的内存 (new, malloc)。

  3. 且 你希望每个对象拥有这份内存的独占权。

车企开发小贴士:

为了避免手写深拷贝这么麻烦且容易出错,现代 C++ (C++11) 给了我们两个神器:

  1. std::vector :它自己内部就实现了深拷贝。如果你的 data 是 vector<int>,你根本不需要写拷贝构造函数,编译器默认生成的就够用了(它会调用 vector 的深拷贝)。

  2. std::shared_ptr / std::unique_ptr :智能指针明确了所有权,不用你手动 new/delete。

相关推荐
沫璃染墨6 分钟前
《Qt从零入门系列(十一):Qt事件机制详解——从QEvent到鼠标、键盘与定时器事件》
c++·qt·ui·硬件工程·交互·个人开发·qt5
2601_9622186130 分钟前
C++中分配器allocator的实现
开发语言·c++
单片机设计·······1 小时前
【dz-1162】汽车前玻璃雨刮器设计
c++·单片机·嵌入式硬件
AI智讯中枢1 小时前
高性能 C++ 实战 (五):perf+FlameGraph 火焰图生产实战,精准定位 CPU / 缓存 / 锁瓶颈,避坑 + 完整实操案例
linux·c++·性能调优·性能分析·perf·flamegraph·火焰图
繁华的地方不一定留下你的脚印1 小时前
C++ 算法与 ranges:用 find_if、transform 写清数据处理意图
开发语言·c++·算法
张小姐的猫1 小时前
【AI大模型接入SDK】 —— 前端页面 & 项目总结与拓展
前端·数据结构·数据库·c++·人工智能·chatgpt
AC赳赳老秦2 小时前
财报附注表格精准提取:OpenClaw 从 PDF 年报附注挖掘隐藏明细,补齐财务分析维度
java·汇编·c++·python·青少年编程·deepseek·openclaw
欧特克_Glodon2 小时前
OpenCV计算机视觉开发入门与实践(基于C++):专栏内容介绍及目录
c++·人工智能·opencv·计算机视觉
码匠许师傅3 小时前
【C++三方组件】Asio 下篇:C++20 协程版 TCP 客户端与服务端
c++·tcp/ip·c++20
一只旭宝3 小时前
【C++复习】四种类型转换 + 常见设计模式 + Redis/MySQL(后端面试复习完整版)
c++·redis·笔记·mysql·设计模式