C++ 深拷贝和浅拷贝详解
- 一、C++ 深拷贝和浅拷贝详解
-
- 1、基本概念
- 2、浅拷贝详解
- 3、深拷贝详解
- 4、深拷贝与浅拷贝的比较
- 5、最佳实践
- 二、示例
-
- 1、代码示例
- 2、运行结果
- 3、关键点解释

一、C++ 深拷贝和浅拷贝详解
在 C++ 编程中,对象的拷贝操作是一个常见需求,尤其在涉及动态内存管理时。拷贝行为分为深拷贝(Deep Copy)和浅拷贝(Shallow Copy),理解它们的区别和实现方式至关重要。
1、基本概念
- 浅拷贝(Shallow Copy):只复制对象本身,而不复制对象指向的底层数据(如指针指向的内存)。多个对象共享同一块内存,可能导致资源管理问题。
- 深拷贝(Deep Copy):不仅复制对象本身,还复制所有底层数据(如指针指向的内存),创建一个完全独立的副本,避免共享资源问题。
在 C++ 中,拷贝行为主要通过拷贝构造函数和赋值运算符实现。默认情况下,编译器生成的拷贝操作是浅拷贝。如果类涉及动态内存分配(如指针成员),则必须自定义深拷贝以避免错误。
2、浅拷贝详解
- 默认行为:当类未自定义拷贝构造函数或赋值运算符时,编译器会生成默认版本,执行浅拷贝。这意味着:
- 对于非指针成员(如 int、double),直接复制值。
- 对于指针成员,只复制指针地址,而不是指针指向的数据。
- 潜在问题:
- 悬挂指针(Dangling Pointer):如果原对象被析构,其指针指向的内存被释放,拷贝对象中的指针仍指向已释放的内存,导致未定义行为。
- 内存泄漏(Memory Leak):如果多个对象共享同一内存,析构时可能多次释放同一块内存,引发崩溃。
- 数据不一致:一个对象修改共享数据会影响其他对象。
以下是一个浅拷贝示例,展示潜在问题:
#include <iostream>
using namespace std;
class ShallowCopy {
private:
int* data; // 指针成员
public:
// 构造函数,分配动态内存
ShallowCopy(int value) {
data = new int(value);
}
// 默认拷贝构造函数(浅拷贝)
ShallowCopy(const ShallowCopy& other) : data(other.data) {} // 只复制指针地址
// 析构函数,释放内存
~ShallowCopy() {
delete data; // 释放动态内存
}
// 打印数据
void print() {
cout << "Data: " << *data << endl;
}
};
int main() {
ShallowCopy obj1(10);
ShallowCopy obj2 = obj1; // 浅拷贝:obj2.data 指向 obj1.data 的同一内存
obj1.print(); // 输出: Data: 10
obj2.print(); // 输出: Data: 10
// obj1 析构时释放内存,obj2 的指针变为悬挂指针
return 0;
}
// 运行后可能崩溃:obj1 析构后,obj2 尝试访问已释放内存
在这个示例中,obj2 通过浅拷贝共享 obj1 的 data 内存。当 obj1 析构时释放内存,obj2 的指针失效,后续操作可能导致崩溃。 
3、深拷贝详解
- 实现方式:通过自定义拷贝构造函数和赋值运算符,显式复制底层数据:
- 拷贝构造函数:为新对象分配新内存,并复制原对象的数据。
- 赋值运算符:类似拷贝构造函数,但需处理自赋值和资源释放。
- 好处:
- 资源安全:每个对象拥有独立内存,避免悬挂指针和内存泄漏。
- 数据隔离:修改一个对象不会影响其他对象。
- 适用场景:当类有指针成员、动态数组、文件句柄等资源时,必须实现深拷贝。
以下是一个深拷贝示例,展示正确实现:
#include <iostream>
using namespace std;
class DeepCopy {
private:
int* data; // 指针成员
public:
// 构造函数
DeepCopy(int value) {
data = new int(value);
}
// 深拷贝构造函数
DeepCopy(const DeepCopy& other) {
data = new int(*other.data); // 分配新内存并复制值
}
// 深拷贝赋值运算符
DeepCopy& operator=(const DeepCopy& other) {
if (this != &other) { // 避免自赋值
delete data; // 释放当前内存
data = new int(*other.data); // 分配新内存并复制值
}
return *this;
}
// 析构函数
~DeepCopy() {
delete data; // 安全释放内存
}
// 打印数据
void print() {
cout << "Data: " << *data << endl;
}
};
int main() {
DeepCopy obj1(20);
DeepCopy obj2 = obj1; // 深拷贝:obj2 有独立内存
obj1.print(); // 输出: Data: 20
obj2.print(); // 输出: Data: 20
obj2 = obj1; // 赋值运算符深拷贝
// 对象析构时不会冲突,内存安全
return 0;
}
在这个示例中,深拷贝确保 obj2 拥有自己的 data 内存副本,避免了浅拷贝的问题。 
4、深拷贝与浅拷贝的比较
-
区别总结:
特性浅拷贝深拷贝 复制内容 只复制指针地址 复制指针地址及指向的数据 内存管理 共享内存,易出错 独立内存,安全 实现复杂度 简单(编译器默认) 复杂(需自定义) 适用性 无动态资源时可用 有动态资源时必须使用 -
何时使用:
- 使用浅拷贝:当类没有指针或动态资源时(如只包含基本类型),默认浅拷贝安全高效。
- 使用深拷贝:当类涉及动态内存分配、文件资源等时,必须实现深拷贝以防止错误。
5、最佳实践
- 遵循 Rule of Three:如果一个类定义了析构函数、拷贝构造函数或赋值运算符中的一个,通常需要定义全部三个,以确保资源管理一致。
- 使用智能指针:在现代 C++ 中,推荐使用智能指针(如 std::unique_ptr 或 std::shared_ptr)代替原始指针,可以自动管理内存,减少深拷贝的手动实现需求。
- 测试验证:在实现深拷贝后,通过单元测试验证拷贝行为,确保无内存错误。
二、示例
1、代码示例
#include <iostream>
#include <stdexcept>
class Matrix {
private:
int** data; // 指向二维数组的指针
size_t rows;
size_t cols;
public:
// 构造函数:分配内存并初始化
Matrix(size_t r, size_t c) : rows(r), cols(c) {
data = new int*[rows];
for (size_t i = 0; i < rows; ++i) {
data[i] = new int[cols](); // 分配并初始化为0
}
std::cout << "Matrix 构造函数: [" << rows << "x" << cols << "]" << std::endl;
}
// 析构函数:释放内存
~Matrix() {
if (data != nullptr) {
for (size_t i = 0; i < rows; ++i) {
delete[] data[i]; // 释放每一行
}
delete[] data; // 释放指针数组
data = nullptr;
}
std::cout << "Matrix 析构函数" << std::endl;
}
// 设置元素值
void setValue(size_t r, size_t c, int value) {
if (r >= rows || c >= cols) {
throw std::out_of_range("索引越界");
}
data[r][c] = value;
}
// 获取元素值
int getValue(size_t r, size_t c) const {
if (r >= rows || c >= cols) {
throw std::out_of_range("索引越界");
}
return data[r][c];
}
// 打印矩阵内容
void print() const {
for (size_t i = 0; i < rows; ++i) {
for (size_t j = 0; j < cols; ++j) {
std::cout << data[i][j] << " ";
}
std::cout << std::endl;
}
}
// ===== 浅拷贝: 使用编译器生成的默认拷贝构造函数和赋值运算符 =====
// 编译器生成的版本只是简单地复制指针(data成员),导致两个对象共享同一块动态内存。
// 这会导致析构时双重释放内存,引发未定义行为(通常是崩溃)。
// ===== 深拷贝: 自定义拷贝构造函数和赋值运算符 =====
// 拷贝构造函数 (深拷贝)
Matrix(const Matrix& other) : rows(other.rows), cols(other.cols) {
std::cout << "Matrix 深拷贝构造函数" << std::endl;
data = new int*[rows];
for (size_t i = 0; i < rows; ++i) {
data[i] = new int[cols];
// 复制数据内容
for (size_t j = 0; j < cols; ++j) {
data[i][j] = other.data[i][j];
}
}
}
// 拷贝赋值运算符 (深拷贝)
Matrix& operator=(const Matrix& other) {
std::cout << "Matrix 深拷贝赋值运算符" << std::endl;
if (this == &other) { // 防止自赋值
return *this;
}
// 释放当前对象的旧资源
if (data != nullptr) {
for (size_t i = 0; i < rows; ++i) {
delete[] data[i];
}
delete[] data;
}
// 复制尺寸
rows = other.rows;
cols = other.cols;
// 分配新内存并复制内容
data = new int*[rows];
for (size_t i = 0; i < rows; ++i) {
data[i] = new int[cols];
for (size_t j = 0; j < cols; ++j) {
data[i][j] = other.data[i][j];
}
}
return *this;
}
};
int main() {
try {
// 创建原始矩阵 mat1
Matrix mat1(2, 3);
mat1.setValue(0, 0, 1);
mat1.setValue(1, 1, 2);
std::cout << "mat1 内容:" << std::endl;
mat1.print();
// 使用深拷贝构造函数创建 mat2 (复制mat1)
Matrix mat2 = mat1; // 调用深拷贝构造函数
std::cout << "mat2 内容 (初始复制自mat1):" << std::endl;
mat2.print();
// 修改 mat2
mat2.setValue(0, 0, 10);
mat2.setValue(0, 1, 20);
std::cout << "修改后的 mat2 内容:" << std::endl;
mat2.print();
// 验证 mat1 未被修改 (独立内存)
std::cout << "修改后 mat1 内容 (应保持原样):" << std::endl;
mat1.print();
// 创建 mat3
Matrix mat3(1, 1);
mat3.setValue(0, 0, 100);
// 使用深拷贝赋值运算符: mat3 = mat2
mat3 = mat2; // 调用深拷贝赋值运算符
std::cout << "mat3 内容 (赋值后等于mat2):" << std::endl;
mat3.print();
// 修改 mat3
mat3.setValue(1, 2, 30);
std::cout << "修改后的 mat3 内容:" << std::endl;
mat3.print();
// 验证 mat2 未被修改 (独立内存)
std::cout << "修改后 mat2 内容 (应保持原样):" << std::endl;
mat2.print();
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
}
return 0;
}
2、运行结果
Matrix 构造函数: [2x3]
mat1 内容:
1 0 0
0 2 0
Matrix 深拷贝构造函数
mat2 内容 (初始复制自mat1):
1 0 0
0 2 0
修改后的 mat2 内容:
10 20 0
0 2 0
修改后 mat1 内容 (应保持原样):
1 0 0
0 2 0
Matrix 构造函数: [1x1]
Matrix 深拷贝赋值运算符
mat3 内容 (赋值后等于mat2):
10 20 0
0 2 0
修改后的 mat3 内容:
10 20 0
0 2 30
修改后 mat2 内容 (应保持原样):
10 20 0
0 2 0
Matrix 析构函数
Matrix 析构函数
Matrix 析构函数
C:\\Users\\徐鹏\\Desktop\\新建文件夹\\Project1\\x64\\Debug\\Project1.exe (进程 29384)已退出,代码为 0 (0x0)。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

3、关键点解释
- 后果: 当其中一个对象被析构时(比如离开作用域),它会释放那块内存。当另一个对象试图使用或释放这个指针时(比如它也被析构),就会发生双重释放,导致程序崩溃或其他未定义行为。
- 示例中未直接演示崩溃,但注释说明了为什么默认行为是危险的。
- 拷贝构造函数 (Matrix(const Matrix& other)): 创建一个新对象时,它会为新对象分配全新的内存,并将原对象的数据逐一复制到新内存中。这样两个对象拥有完全独立的数据副本。
- 拷贝赋值运算符 (operator=): 当将一个对象赋值给另一个现有对象时:
- 首先检查是否是自赋值 (if (this == &other)),避免不必要的操作和潜在错误。
- 释放目标对象(this)原有的内存资源。
- 复制源对象(other)的尺寸信息。
- 分配新的内存空间给目标对象。
- 复制源对象的数据到目标对象的新内存中。
- 返回 *this 以支持链式赋值。
- 创建 mat1 并设置值。
- 使用深拷贝构造函数创建 mat2(初始内容是 mat1 的副本)。
- 修改 mat2 后,打印 mat1 显示其内容未受影响,证明内存独立。
- 创建 mat3 并设置一个值。
- 使用深拷贝赋值运算符将 mat2 赋值给 mat3。
- 修改 mat3 后,打印 mat2 显示其内容未受影响,再次证明内存独立。

![打卡信奥刷题(3676)用C++实现信奥题 P12271 [蓝桥杯 2024 国 Python B] 括号与字母-171主机测评](https://www.171host.com/wp-content/uploads/2026/11/20261107050125-6aeeb0a5a5ade-220x150.png)