深拷贝(Deep Copy)和浅拷贝(Shallow Copy)

深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是处理对象复制时非常重要的概念,尤其是在对象内部包含指针或引用指向动态分配的内存时。它们的主要区别在于如何处理这些内部资源。


1. 浅拷贝 (Shallow Copy)

情景: 你想把笔记本里的信息给你的朋友。

操作: 你没有把整个笔记本复制一份,而是只复制了笔记本的"目录" ,或者说,你只告诉了你的朋友:"这些信息在我的笔记本的第几页、第几行。"

结果:

  • 你的朋友也"拥有"了这些信息,但实际上,他只是知道去哪里看你的笔记本。

  • 问题来了:

    • 如果 在你的笔记本里修改了某个信息(比如把"苹果"改成了"香蕉"),那么你的朋友下次按照他手里的目录去看的时候,他看到的也是"香蕉"。因为你们俩的"目录"都指向同一个实际的笔记本
    • 如果把你的笔记本丢了或者烧了,那么你的朋友手里的目录就彻底没用了,因为他再也找不到那些信息了。
    • 更糟糕的是,如果你的朋友也"丢弃"了他手里的目录(他以为他丢弃的是信息),而你又丢弃了你的笔记本,那么同一份信息就被"丢弃"了两次,这会引起混乱甚至错误。

总结: 浅拷贝就像是共享一个链接。大家看到的是同一个内容,内容变了,大家看到的都变。如果源头没了,链接就失效了。(类似于我们经常做的配钥匙,而不是复制房子)


  • 含义 : 浅拷贝只复制对象本身的值,而不复制对象内部指向的动态分配的资源。它只是复制了指向这些资源的指针或引用
  • 工作方式 : 当你进行浅拷贝时,新对象会拥有与原始对象相同的成员变量值。如果这些成员变量是基本类型(如 int, char, double),那么它们的值会被直接复制。但如果成员变量是指针或引用,那么新对象会复制这个指针或引用的地址,而不是它所指向的数据。
  • 结果 : 原始对象和新对象会共享同一块动态分配的内存。
  • 问题 :
    • 修改互相影响: 如果通过新对象修改了共享内存中的数据,原始对象也会看到这些修改,反之亦然。
    • 二次释放 (Double Free): 当原始对象和新对象都被销毁时,它们会尝试释放同一块内存两次,这会导致程序崩溃或未定义行为。

例子 (C++ 伪代码):

cpp 复制代码
class MyClass {
public:
    int* data;
    MyClass(int val) {
        data = new int(val); // 动态分配内存
    }
    // 默认的拷贝构造函数或赋值运算符会执行浅拷贝
    // MyClass(const MyClass& other) {
    //     data = other.data; // 只是复制了指针地址
    // }
    ~MyClass() {
        delete data; // 析构时释放内存
    }
};

MyClass obj1(10); // obj1.data 指向一块内存,里面是 10
MyClass obj2 = obj1; // 浅拷贝,obj2.data 和 obj1.data 指向同一块内存

// 此时 obj1.data 和 obj2.data 都指向同一个地址
// 如果通过 obj2 修改数据:
*obj2.data = 20;
// 那么 *obj1.data 也会变成 20

// 当 obj1 和 obj2 销毁时,会尝试对同一块内存 delete 两次,导致错误。

2. 深拷贝 (Deep Copy)

情景: 你想把笔记本里的信息给你的朋友。

操作: 你不是只告诉朋友目录,而是把你的笔记本里的所有信息都一字不差地抄写了一份 ,然后用一个全新的笔记本把这些抄写出来的信息装好,再把这个新笔记本给了你的朋友。

结果:

  • 现在,你有一个笔记本,你的朋友也有一个笔记本。

  • 这两个笔记本里的信息内容一模一样,但它们是完全独立的两份信息。

  • 好处:

    • 如果在你的笔记本里修改了某个信息(比如把"苹果"改成了"香蕉"),你的朋友的笔记本里仍然是原来的"苹果"。你们互不影响。
    • 如果把你的笔记本丢了或者烧了,你的朋友的笔记本仍然完好无损,他可以继续看他的信息。
    • 你们各自管理自己的笔记本和信息,不会互相干扰,也不会出现"丢弃两次"的问题。

总结: 深拷贝就像是复制一份实体文件。大家都有自己的一份,互不影响。


  • 含义 : 深拷贝不仅复制对象本身的值,还会为对象内部指向的动态分配的资源重新分配内存,并将原始对象中的数据复制到新分配的内存中。
  • 工作方式: 当你进行深拷贝时,新对象会为所有动态分配的资源创建独立的副本。这意味着如果原始对象有一个指针指向一块内存,新对象会分配一块新的内存,并将原始指针指向的数据复制到这块新内存中,然后新对象的指针指向这块新内存。
  • 结果 : 原始对象和新对象拥有完全独立的资源。
  • 优点 :
    • 独立性: 修改其中一个对象不会影响另一个对象。
    • 安全: 不会发生二次释放的问题,因为每个对象都管理自己的资源。

例子 (C++ 伪代码):

cpp 复制代码
class MyClass {
public:
    int* data;
    MyClass(int val) {
        data = new int(val);
    }
    // 深拷贝构造函数
    MyClass(const MyClass& other) {
        data = new int(*other.data); // 为新对象重新分配内存,并复制数据
    }
    // 深拷贝赋值运算符
    MyClass& operator=(const MyClass& other) {
        if (this != &other) { // 防止自赋值
            delete data; // 释放旧资源
            data = new int(*other.data); // 分配新资源并复制数据
        }
        return *this;
    }
    ~MyClass() {
        delete data;
    }
};

MyClass obj1(10); // obj1.data 指向一块内存,里面是 10
MyClass obj2 = obj1; // 深拷贝,obj2.data 指向另一块内存,里面也是 10

// 此时 obj1.data 和 obj2.data 指向不同的地址
// 如果通过 obj2 修改数据:
*obj2.data = 20;
// 那么 *obj1.data 仍然是 10,不受影响。

// 当 obj1 和 obj2 销毁时,它们各自释放自己的内存,不会冲突。

区别总结

特性 浅拷贝 (Shallow Copy) 深拷贝 (Deep Copy)
复制内容 复制对象本身的值和内部指针/引用的地址 复制对象本身的值,并为内部指针/引用指向的资源重新分配内存并复制数据
资源共享 原始对象和新对象共享动态分配的资源。 原始对象和新对象拥有独立的动态分配资源。
修改影响 修改其中一个对象会影响另一个对象。 修改其中一个对象不会影响另一个对象。
内存释放 容易导致二次释放问题。 安全,每个对象管理自己的资源。
实现方式 默认的拷贝构造函数/赋值运算符通常是浅拷贝。 需要手动实现拷贝构造函数和赋值运算符,以处理动态资源。
效率 相对高效,因为只复制指针地址。 相对低效,因为需要分配新内存并复制所有数据。

何时使用

  • 浅拷贝: 当对象不包含指向动态分配内存的指针或引用时(例如,只包含基本类型或不涉及资源管理的复合类型),或者你明确希望两个对象共享同一份数据时。
  • 深拷贝: 当对象包含指向动态分配内存的指针或引用,并且你需要确保新对象拥有自己独立的数据副本时。这是处理资源管理(如文件句柄、网络连接、动态数组等)的类通常需要的方式。

在 C++ 中,如果你的类管理着动态内存或其他资源,通常需要遵循"三/五/零法则"(Rule of Three/Five/Zero),即如果需要自定义析构函数,那么通常也需要自定义拷贝构造函数和拷贝赋值运算符(或者移动构造函数和移动赋值运算符),以确保正确的深拷贝行为,避免资源泄漏和二次释放问题。


简单来说:

  • 浅拷贝 :只复制"地址"或"引用",就像复制了一份地图 。两份地图都指向同一个地方。这个地方变了,两份地图看到的结果都变。如果这个地方被毁了,两份地图都失效。
  • 深拷贝 :复制了"地址"指向的实际内容 ,就像复制了一份一模一样的地方 。两份地图指向的是两个独立但内容相同的地方。一个地方变了,另一个地方不受影响。一个地方被毁了,另一个地方依然存在。
相关推荐
blasit2 天前
笔记:Qt C++建立子线程做一个socket TCP常连接通信
c++·qt·tcp/ip
肆忆_3 天前
# 用 5 个问题学懂 C++ 虚函数(入门级)
c++
不想写代码的星星3 天前
虚函数表:C++ 多态背后的那个男人
c++
端平入洛5 天前
delete又未完全delete
c++
端平入洛6 天前
auto有时不auto
c++
哇哈哈20217 天前
信号量和信号
linux·c++
多恩Stone7 天前
【C++入门扫盲1】C++ 与 Python:类型、编译器/解释器与 CPU 的关系
开发语言·c++·人工智能·python·算法·3d·aigc
蜡笔小马7 天前
21.Boost.Geometry disjoint、distance、envelope、equals、expand和for_each算法接口详解
c++·算法·boost
超级大福宝7 天前
N皇后问题:经典回溯算法的一些分析
数据结构·c++·算法·leetcode