为什么要用深拷贝 - C++

0 阅读5分钟

为什么要用深拷贝

在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;
 }

通过对象初始化新对象触发默认拷贝构造函数,运行代码会直接崩溃,核心问题为堆内存重复释放

image.png

上述代码运行时,h1h2area指针指向同一块堆内存

  • 程序结束时,析构函数会先释放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

从结果可看出:

  • h1h2的堆内存地址完全不同,实现了资源独立;
  • 修改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;
 }

总结

  1. 浅拷贝:编译器默认实现,仅逐值拷贝成员变量,仅适用于无堆内存指针的简单类;含堆内存指针时,会导致多个对象共享堆内存,引发重复释放内存资源篡改
  2. 深拷贝:通过自定义拷贝构造函数+赋值运算符重载实现,为新对象分配独立的堆内存并拷贝内容,使对象资源完全独立,解决浅拷贝的所有问题。
  3. 使用场景:当自定义类包含堆内存指针、动态数组、容器等需要手动管理的资源时,必须实现深拷贝。
  4. 核心原则:深拷贝的本质是拷贝资源本身,而非资源的地址;浅拷贝则是拷贝资源地址,而非资源本身。
  5. 通俗易懂浅拷贝就是被复制的和复制的是同一片地址;而深拷贝的话是两个不同的地址。