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. 为了拷贝 lidar1other,系统需要再次调用拷贝构造函数

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

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

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


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

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

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

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

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

车企开发小贴士:

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

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

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

相关推荐
咩咦12 分钟前
C++学习笔记22:前置后置 ++/-- 和日期减日期
c++·学习笔记·运算符重载·日期类·前置++·后置++·日期减日期
计算机安禾16 分钟前
【c++面向对象编程】第40篇:单例模式(Singleton)的多种C++实现
开发语言·c++·单例模式
一个不知名程序员www44 分钟前
算法学习入门---算法题DAY1
c++·算法
桀人1 小时前
C++——内存管理——new和delete的超详细解析
开发语言·c++
Shadow(⊙o⊙)1 小时前
Shell进程替换,自定义Shell解释器——字符串库函数灵活操作!
linux·运维·服务器·开发语言·c++·学习
_F_y1 小时前
树形 DP 从入门到进阶:普通树形DP、树形背包、换根DP
c++·动态规划
Hua-Jay1 小时前
OpenCV联合C++/Qt 学习笔记(二十三)----图像校正及单目位姿估计
c++·笔记·qt·opencv·学习·计算机视觉
charlie1145141912 小时前
现代C++特性指南(4)——完美转发与移动语义实战
开发语言·c++·现代c++
小白|2 小时前
cann-learning-hub:昇腾CANN社区学习中心完全指南
java·c++·算法
mirror_zAI2 小时前
C++ 仿 QQ 聊天室项目:Qt 客户端 + epoll 服务端 + Reactor 架构(含源码)
c++·qt·架构