在 C&C++ 中,深拷贝(Deep Copy) 和 浅拷贝(Shallow Copy) 是对象复制的两种根本不同的方式,主要区别在于如何处理指针成员指向的资源。理解它们的区别对内存管理和程序稳定性至关重要。
关于C的深拷贝与浅拷贝
c语言中的深拷贝和浅拷贝 - 知乎
推荐博文
c语言中的深拷贝和浅拷贝 - 知乎
C语言 - 深拷贝与浅拷贝详解_c语言 deepcopy-CSDN博客
【C++ 深拷贝与浅拷贝详解】_c++浅拷贝和深拷贝-CSDN博客
【C++ Primer】 浅拷贝和深拷贝的实现 - 知乎
1. 浅拷贝(Shallow Copy)
原理:仅复制对象的成员值(包括指针的值),不复制指针指向的实际资源。
结果:原对象和拷贝对象的指针成员指向同一块内存地址。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class Shallow {
public:
int* data;
Shallow(int d) {
data = new int(d); // 分配堆内存
}
// 编译器生成的默认拷贝构造函数(浅拷贝)
Shallow(const Shallow& other) : data(other.data) {}
~Shallow() { delete data; }
};
// 使用示例
Shallow obj1(10);
Shallow obj2 = obj1; // 浅拷贝:obj2.data 指向 obj1.data 的内存
*obj1.data = 20; // 修改 obj1
cout << *obj2.data; // 输出 20(obj2 也被修改)
// main结束时:obj2 和 obj1 会先后调用析构函数
// 导致同一内存被 delete 两次 → 程序崩溃!
|
浅拷贝的核心问题:
- 悬空指针:一个对象释放资源后,另一个对象的指针失效
- 重复释放:多个对象尝试释放同一块内存 → 崩溃
- 数据篡改:通过一个对象修改数据会影响其他对象
2. 深拷贝(Deep Copy)
原理:不仅复制对象成员,还复制指针指向的资源。
结果:原对象和拷贝对象拥有完全独立的资源副本。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
class Deep {
public:
int* data;
Deep(int d) {
data = new int(d);
}
// 自定义拷贝构造函数(深拷贝)
Deep(const Deep& other) {
data = new int(*other.data); // 关键:分配新内存并复制值
}
// 深拷贝赋值运算符
Deep& operator=(const Deep& other) {
if (this != &other) { // 防止自赋值
delete data; // 释放原有资源
data = new int(*other.data); // 创建新副本
}
return *this;
}
~Deep() { delete data; }
};
// 使用示例
Deep obj1(10);
Deep obj2 = obj1; // 深拷贝:obj2 拥有独立的内存副本
*obj1.data = 20; // 修改 obj1
cout << *obj2.data; // 输出 10(obj2 不受影响)
// 析构时各自释放独立内存 → 安全
|
深拷贝的核心优势:
- 资源独立:每个对象拥有自己的资源副本
- 安全析构:不会发生重复释放
- 数据隔离:修改一个对象不影响其他对象
3. 关键对比表
特性 |
浅拷贝 |
深拷贝 |
复制内容 |
指针值(内存地址) |
指针指向的实际数据 |
资源副本 |
共享同一份资源 |
创建独立资源副本 |
内存开销 |
小 |
大(需额外分配内存) |
安全性 |
危险(悬空指针/重复释放) |
安全 |
默认行为 |
编译器默认生成 |
需要手动实现 |
修改影响 |
影响所有副本 |
只影响当前对象 |
适用场景 |
无资源管理的简单对象 |
含指针/动态资源的对象 |
4. 何时需要深拷贝?
当类满足以下任一条件时必须实现深拷贝:
- 包含原始指针成员指向动态分配的内存
- 管理独占资源(文件句柄、网络连接等)
- 需要独立副本逻辑(如字符串、容器类)
Rule of Three 原则:如果类需要自定义析构函数,则通常也需要自定义拷贝构造函数和拷贝赋值运算符(即深拷贝)。
5. 现代C++的优化方案
(1) 禁用拷贝
1
2
3
4
5
6
|
class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete; // 禁用拷贝
NonCopyable& operator=(const NonCopyable&) = delete;
};
|
(2) 使用智能指针(推荐)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#include <memory>
class SafeObject {
std::unique_ptr<int> data; // 独占所有权
public:
SafeObject(int d) : data(std::make_unique<int>(d)) {}
// 不需要自定义拷贝构造/赋值
// unique_ptr 禁止拷贝,但支持移动
};
// shared_ptr 版本(共享所有权,引用计数)
class SharedResource {
std::shared_ptr<MyResource> res;
};
|
(3) 移动语义(C++11+)
1
2
3
4
5
6
7
8
9
|
class Movable {
int* data;
public:
// 移动构造函数(转移资源所有权)
Movable(Movable&& other) noexcept
: data(other.data) {
other.data = nullptr; // 置空原对象
}
};
|
6. 深拷贝的典型应用场景
- 字符串类:
std::string
内部实现深拷贝
- 容器类:
std::vector
复制时深拷贝所有元素
- 自定义资源管理:数据库连接池、图像缓冲区等
- 多线程安全:避免共享资源需要深拷贝
总结
概念 |
本质 |
解决方案 |
浅拷贝 |
复制指针(共享资源) |
编译器默认行为 |
深拷贝 |
复制资源内容(独立副本) |
自定义拷贝构造/赋值运算符 |
现代实践 |
避免手动深拷贝 |
使用智能指针+移动语义 |
最佳实践:
- 优先使用
std::vector
, std::string
等已安全实现的容器
- 用
std::unique_ptr
/std::shared_ptr
替代原始指针
- 遵循 Rule of Zero:通过组合安全类型避免自定义拷贝/析构
- 对于必须深拷贝的大型对象,考虑添加移动语义优化性能