浅拷贝和深拷贝是针对包含指针/动态内存成员的类对象的两种拷贝方式,核心差异在于:是否为指针成员重新分配内存,还是仅复制指针本身
可以用一个一个比喻理解:
浅拷贝:你有一把钥匙(指针)开了一个房间(堆内存),浅拷贝只是复制了这把钥匙,两个钥匙都能开同一个房间;
深拷贝:不仅复制钥匙,还重新建了一个一模一样的房间,两把钥匙开各自的房间,互不影响。
一、浅拷贝
1、原理
浅拷贝是c++编译器默认升成的拷贝构造函数/赋值运算符的行为:
·对普通成员变量(如int、string):直接复制值
·对指针成员变量:仅复制指针的地址(而非指针指向的堆内存内容);
·最终结果:原对象和拷贝对象的指针指向同一块堆内存
2、代码示例(浅拷贝的问题)
cpp
#include <iostream>
#include <cstring>
using namespace std;
class String {
private:
char* str; // 动态分配内存的指针成员
public:
// 构造函数
String(const char* s = "") {
str = new char[strlen(s) + 1]; // 堆上分配内存
strcpy(str, s);
}
// 析构函数:释放堆内存
~String() {
delete[] str; // 释放指针指向的内存
}
// 打印字符串
void print() const {
cout << str << endl;
}
};
int main() {
String s1("Hello");
String s2 = s1; // 浅拷贝:s2.str 和 s1.str 指向同一块内存
s1.print(); // 输出 Hello
s2.print(); // 输出 Hello
// 程序结束时,s2先析构,释放了内存;s1析构时再次释放同一块内存,导致**双重释放**(程序崩溃)
return 0;
}
问题分析:
s2 是 s1 的浅拷贝,两者的str指针指向同一块堆内存;
析构时,s2 先释放内存,s1 析构时尝试释放已释放的内存,触发内存错误(double free)。
二. 深拷贝(Deep Copy)
深拷贝需要手动实现拷贝构造函数和赋值运算符重载,其行为是:
不仅复制对象的成员变量,还会为指针成员重新分配独立的堆内存;
复制指针指向的内存内容到新分配的内存中;
最终两个对象拥有独立的内存空间,互不影响。
cpp
#include <iostream>
#include <cstring>
using namespace std;
class String {
private:
char* str;
public:
// 构造函数
String(const char* s = "") {
str = new char[strlen(s) + 1];
strcpy(str, s);
}
// 深拷贝构造函数(核心)
String(const String& other) {
// 1. 为当前对象分配独立的堆内存
str = new char[strlen(other.str) + 1];
// 2. 复制other指针指向的内容到新内存
strcpy(str, other.str);
}
// 深拷贝赋值运算符重载(核心)
String& operator=(const String& other) {
// 防止自赋值(s1 = s1)
if (this == &other) {
return *this;
}
// 1. 释放当前对象已有的堆内存
delete[] str;
// 2. 分配新内存并复制内容
str = new char[strlen(other.str) + 1];
strcpy(str, other.str);
return *this;
}
// 析构函数
~String() {
delete[] str;
}
// 打印字符串
void print() const {
cout << str << endl;
}
// 修改字符串(验证独立性)
void set(const char* s) {
delete[] str;
str = new char[strlen(s) + 1];
strcpy(str, s);
}
};
int main() {
String s1("Hello");
String s2 = s1; // 深拷贝:s2有独立的内存
s1.set("World"); // 修改s1的内容,s2不受影响
s1.print(); // 输出 World
s2.print(); // 输出 Hello
return 0;
}
关键说明:
拷贝构造函数:先为当前对象分配内存,再复制内容;
赋值运算符重载:先释放自身内存→防止自赋值→分配新内存→复制内容;
最终 s1 和 s2 的str指向不同内存,修改其中一个不会影响另一个。
a)浅拷贝
1、浅拷贝的内存表现
假设我们有一个包含动态内存的类对象,其内存布局如下(以 32 位系统为例):`对象 s1:
±---------+
| 指针 str | → 0x1000 (堆内存,存储 "Hello")
±---------+
浅拷贝生成 s2:
±---------+
| 指针 str | → 0x1000 (和s1指向同一块堆内存)
±---------+
- 浅拷贝的执行逻辑
编译器生成的默认拷贝构造函数伪代码(逻辑等价):
cpp
// 编译器自动生成的浅拷贝构造函数
String::String(const String& other) {
// 直接复制指针的值(地址),而非指针指向的内容
this->str = other.str;
}
- 浅拷贝的核心问题根源
内存所有权模糊:两个对象都认为自己拥有0x1000这块堆内存;
析构时双重释放:第一个对象析构释放0x1000,第二个对象析构时再次释放已失效的内存,触发操作系统的内存保护机制(程序崩溃);
修改相互影响:修改s1.str指向的内容,s2.str也会同步变化,违背对象的独立性原则。
b)深拷贝
深拷贝的本质是值复制(value copy)+ 独立内存分配,手动实现的拷贝逻辑会:
为目标对象分配全新的、独立的堆内存;
将源对象指针指向的数据内容复制到新内存中;
让目标对象的指针指向这个新内存,而非源对象的内存地址。
-
内存层面的表现
对象 s1:
±---------+
| 指针 str | → 0x1000 (堆内存,存储 "Hello")
±---------+
深拷贝生成 s2:
±---------+
| 指针 str | → 0x2000 (新分配的堆内存,复制了 "Hello")
±---------+
-
深拷贝的执行逻辑
手动实现的深拷贝构造函数伪代码(逻辑等价):
cpp
// 手动实现的深拷贝构造函数
String::String(const String& other) {
// 步骤1:计算源对象数据的长度,为目标对象分配独立堆内存
size_t len = strlen(other.str) + 1;
this->str = new char[len]; // 新内存地址,比如0x2000
// 步骤2:复制源对象指针指向的内容到新内存
memcpy(this->str, other.str, len);
}
- 深拷贝解决问题的核心逻辑
内存所有权唯一:每个对象拥有自己独立的堆内存,析构时只释放自己的内存,不会冲突;
数据独立性:修改其中一个对象的堆内存数据,不会影响另一个对象,因为它们指向不同的内存块;
避免野指针:赋值运算符重载时先释放自身旧内存,再分配新内存,杜绝内存泄漏和野指针

总结
浅拷贝是编译器默认的逐字节复制,仅复制指针地址,适用于无动态内存的简单类,但会导致多个对象共享堆内存,引发内存错误;
深拷贝需要手动实现,为指针成员重新分配独立内存并复制内容,保证对象独立性,是处理动态内存类的必选方案;
只要类中包含new/delete管理的动态内存,就必须手动实现深拷贝(拷贝构造函数 + 赋值运算符重载),否则会触发浅拷贝的内存安全问题。