为什么要用深拷贝
在C++中,当类包含堆内存指针成员时,编译器默认的浅拷贝会引发内存重复释放、资源被非法篡改等致命问题,
深拷贝是解决该类问题的核心手段。
浅拷贝的问题
C++编译器会自动生成默认拷贝构造函数,其逻辑为成员变量逐值拷贝。
该行为对基本数据类型无影响,但对指向堆内存的指针,仅拷贝内存地址,而非指针指向的堆内存内容,最终导致多个对象共享同一块堆内存,引发程序崩溃。
#include <iostream>
#include <cstring>
using namespace std;
class House {
private:
int* area;
public:
House(int a) : area(new int(a)) {
cout << "构造函数执行,堆内存地址:" << (void*)area << endl;
}
~House() {
if (area != nullptr) {
delete area;
area = nullptr;
cout << "析构函数执行,释放堆内存:" << (void*)area << endl;
}
}
void setArea(int a) { *area = a; }
void showInfo() const {
cout << "面积:" << *area << ",堆内存地址:" << (void*)area << endl;
}
};
int main() {
House h1(120);
House h2 = h1; // 触发默认浅拷贝,仅拷贝area的地址
// 打印两个对象的信息,验证指针指向同一堆内存
h1.showInfo();
h2.showInfo();
return 0;
}
通过对象初始化新对象触发默认拷贝构造函数,运行代码会直接崩溃,核心问题为堆内存重复释放。
上述代码运行时,h1和h2的area指针指向同一块堆内存:
- 程序结束时,析构函数会先释放
h2的堆内存,再释放h1的堆内存; - 对已释放的堆内存再次执行
delete,触发C++内存访问错误,程序直接崩溃。 - 额外问题:修改
h1.setArea(150),h2的面积也会被篡改,因为二者共享堆内存资源。
深拷贝
深拷贝的核心逻辑:为新对象重新分配独立的堆内存,再将原对象堆内存的内容拷贝至新内存,使拷贝后的对象与原对象拥有相同属性但独立的资源,彼此的内存操作互不影响。
仅在原有House类中添加自定义拷贝构造函数即可,其余代码不变:
#include <iostream>
#include <cstring>
using namespace std;
class House {
private:
int* area;
public:
House(int a) : area(new int(a)) {
cout << "构造函数执行,堆内存地址:" << (void*)area << endl;
}
// 自定义深拷贝构造函数:核心实现
House(const House& other) {
// 1. 为新对象分配独立的堆内存
this->area = new int;
// 2. 将原对象堆内存的内容,拷贝至新堆内存
*this->area = *other.area;
cout << "深拷贝构造函数执行,新堆内存地址:" << (void*)this->area << endl;
}
~House() {
if (area != nullptr) {
delete area;
area = nullptr;
cout << "析构函数执行,释放堆内存:" << (void*)area << endl;
}
}
void setArea(int a) { *area = a; }
void showInfo() const {
cout << "面积:" << *area << ",堆内存地址:" << (void*)area << endl;
}
};
int main() {
House h1(120); // 构造h1,分配堆内存
House h2 = h1; // 触发深拷贝,为h2分配新的堆内存
// 打印信息:验证两个对象堆内存地址不同,内容相同
cout << "h1:"; h1.showInfo();
cout << "h2:"; h2.showInfo();
// 修改h1的面积,验证h2不受影响
h1.setArea(150);
cout << "修改h1面积后:" << endl;
cout << "h1:"; h1.showInfo();
cout << "h2:"; h2.showInfo();
return 0; // 析构h2和h1,释放各自的堆内存,程序正常结束
}
同样的主函数逻辑,深拷贝下两个对象拥有独立堆内存,无重复释放问题,且修改一个对象不会影响另一个。
构造函数执行,堆内存地址:0x55f8d2a8eeb0
深拷贝构造函数执行,新堆内存地址:0x55f8d2a8ef00
h1:面积:120,堆内存地址:0x55f8d2a8eeb0
h2:面积:120,堆内存地址:0x55f8d2a8ef00
修改h1面积后:
h1:面积:150,堆内存地址:0x55f8d2a8eeb0
h2:面积:120,堆内存地址:0x55f8d2a8ef00
析构函数执行,释放堆内存:0x0
析构函数执行,释放堆内存:0x0
从结果可看出:
h1和h2的堆内存地址完全不同,实现了资源独立;- 修改
h1的面积,h2无变化,避免了资源篡改; - 析构函数正常释放各自的堆内存,无重复释放问题,程序正常结束。
深拷贝需配合赋值运算符重载
若在代码中使用赋值操作(h2 = h1),仅自定义拷贝构造函数仍会触发浅拷贝问题,需同时重载赋值运算符,实现赋值操作的深拷贝。
在House类中添加赋值运算符重载函数,核心逻辑与深拷贝构造函数一致:
// 重载赋值运算符,实现赋值操作的深拷贝
House& operator=(const House& other) {
// 防止自赋值(h1 = h1)
if (this == &other) {
return *this;
}
// 释放当前对象已有的堆内存,避免内存泄漏
if (this->area != nullptr) {
delete this->area;
}
// 重新分配堆内存,拷贝内容
this->area = new int;
*this->area = *other.area;
cout << "赋值运算符深拷贝,新堆内存地址:" << (void*)this->area << endl;
return *this;
}
总结
- 浅拷贝:编译器默认实现,仅逐值拷贝成员变量,仅适用于无堆内存指针的简单类;含堆内存指针时,会导致多个对象共享堆内存,引发重复释放内存和资源篡改。
- 深拷贝:通过自定义拷贝构造函数+赋值运算符重载实现,为新对象分配独立的堆内存并拷贝内容,使对象资源完全独立,解决浅拷贝的所有问题。
- 使用场景:当自定义类包含堆内存指针、动态数组、容器等需要手动管理的资源时,必须实现深拷贝。
- 核心原则:深拷贝的本质是拷贝资源本身,而非资源的地址;浅拷贝则是拷贝资源地址,而非资源本身。
- 通俗易懂浅拷贝就是被复制的和复制的是同一片地址;而深拷贝的话是两个不同的地址。