C++深浅拷贝解析:从默认浅拷贝问题到自定义深拷贝实现

星野
星野
发布时间:2026-10-16 | 5人浏览过
原创

C++中的对象拷贝分为浅拷贝与深拷贝。若类管理动态分配的内存(如指针成员),使用编译器自动生成的拷贝构造函数会导致浅拷贝,引发数据冲突和双重释放风险。通过重载拷贝构造函数实现深拷贝,可使拷贝后的对象拥有独立的内存空间,彻底解决此类安全隐患。

背景与问题分析

在面向对象编程中,类的组合关系十分常见。例如,一辆汽车(Car)拥有一个引擎(Engine)。通常情况下,如果类仅由基本数据类型或没有动态资源的对象组成,C++编译器会自动生成一个拷贝构造函数,它能安全地完成对象的复制。

然而,一旦类中包含指向堆内存的指针,情况就会发生变化。默认情况下,这个隐式的拷贝构造函数只会进行“值”的拷贝——对于指针而言,就是拷贝地址。这意味着原始对象和副本将指向内存中的同一块区域。这种机制被称为浅拷贝。

下面首先构建两个类:Engine表示引擎,Car表示汽车。为了模拟真实的资源管理场景,Car类内部使用了指针来管理Engine对象的实例。

核心类定义 (Car.h & Car.cpp)

Car类负责维护品牌、版本以及引擎指针。其中 setEngine 方法负责在堆上分配新的 Engine 对象。

#ifndef COPY__CAR_H_
#define COPY__CAR_H_
#include "Engine.h"
#include <string>
using namespace std;
class Car {
 public:
  // 构造函数
  Car();
  Car(string brand, int version);
  ~Car();
  // 添加或者修改一个引擎
  void setEngine(string engine_brand, int engine_version);
  // 对汽车信息进行描述
  string description() const;
 private:
  string brand; // 品牌
  int version; // 型号
  Engine *engine; // 引擎指针
};
#endif //COPY__CAR_H_
#include "Car.h"
#include <sstream>

Car::Car() {
  this->brand = "无";
  this->version = 0;
  this->engine = nullptr;
}

Car::Car(string brand, int version) {
  engine = nullptr;
  this->brand = brand;
  this->version = version;
}

Car::~Car() {
  // 注意:若未实现深拷贝,析构时需小心处理已被共享或删除的内存
  if(engine) delete engine;
}

void Car::setEngine(string engine_brand, int engine_version) {
  if (engine) {
    delete engine;
  }
  engine = new Engine(engine_brand, engine_version);
}

string Car::description() const {
  stringstream result;
  if (engine) {
      result << "品牌:" << brand << " 版本:" << version 
             << engine->description();
  } else {
      result << "品牌:" << brand << " 版本:" << version;
  }
  return result.str();
}

引擎类定义 (Engine.h & Engine.cpp)

#ifndef COPY__ENGINE_H_
#define COPY__ENGINE_H_
#include <string>
using namespace std;
class Engine {
 public:
  Engine();
  Engine(string brand, int version);
  ~Engine();
  string description() const;
 private:
  string brand;
  int version;
};
#endif //COPY__ENGINE_H_
#include "Engine.h"
#include <sstream>

Engine::Engine() {
  this->brand = "无";
  this->version = 0;
}

Engine::Engine(string brand, int version) {
  this->brand = brand;
  this->version = version;
}

Engine::~Engine() {
  // 清理引擎资源
}

string Engine::description() const {
  stringstream result;
  result << " 发动机品牌:" << brand << " 发动机版本:" << version;
  return result.str();
}

浅拷贝引发的隐患

让我们在一个测试主函数中观察不使用自定义拷贝构造函数的行为。这里我们创建一辆车 car_1,设置其引擎为宝马,然后通过拷贝构造创建第二辆车 car_2。

#include "Car.h"
#include <iostream>
using namespace std;
int main() {
  // 1. 创建 car_1 对象
  Car car_1("宝马", 1);
  // 2. 为 car_1 设置引擎
  car_1.setEngine("宝马", 1);
  
  // 3. 创建 car_2 对象,调用编译器生成的默认拷贝构造函数
  Car car_2(car_1);
  
  cout << "修改前:" << endl;
  cout << car_1.description() << endl;
  cout << car_2.description() << endl;

  // 4. 修改 car_2 的引擎
  car_2.setEngine("奔驰", 1);

  cout << "修改后:" << endl;
  cout << car_1.description() << endl;
  cout << car_2.description() << endl;

  return 0;
}

运行上述代码后,我们会观察到出乎意料的结果:car_1 的引擎信息竟然也被变成了奔驰。

原因分析:
在使用了编译器自动生成的拷贝构造函数时,car_2.engine 直接复制了 car_1.engine 的地址值。此时,两个对象的指针都指向堆内存中的同一块 Engine 对象区域。

当我们在第二步中调用 car_2.setEngine(...) 时:

  1. 程序会释放当前指针指向的内存(即那个共同的宝马引擎)。
  2. 在堆上开辟新空间存放奔驰引擎,并将 car_2.engine 指向它。

由于操作并未涉及 car_1 的指针本身,而是改变了它所指向的资源(如果在第一步没删的话)或者更危险的情况是,如果仅仅是简单的指针赋值而不考虑逻辑隔离,在复杂的交互下极易产生数据串扰。在此特定代码逻辑中,`setEngine` 先 `delete` 了旧对象再 `new` 了新对象。但因为两者最初指向同一个,第一次 `delete` 后内存已失效,第二次修改实际上是破坏了共用资源。更致命的风险在于析构阶段:当函数结束,car_2 析构删除了其引擎;随后 car_1 析构时再次尝试删除那块已无效的内存,将导致 程序崩溃 (Double Free)。

解决方案:手动实现深拷贝

要避免上述问题,必须实现深拷贝 (Deep Copy)。深拷贝要求不仅复制对象本身的数据,还要为其中的动态资源分配全新的内存空间,并复制实际内容。这样,副本对象拥有了完全独立的生命周期和资源副本。

我们需要在 Car 类中显式声明并定义拷贝构造函数。

// 在 Car.h 中添加声明
Car(const Car &other);

在 Car.cpp 中补充实现:

Car::Car(const Car &other) {
  // 1. 复制基本类型成员
  this->brand = other.brand;
  this->version = other.version;
  
  // 2. 针对指针成员,手动分配新内存并复制内部状态(深拷贝核心)
  if (other.engine) {
      this->engine = new Engine(other.brand, other.version);
  } else {
      this->engine = nullptr;
  }
}

加入深拷贝实现后重新运行测试代码:

结果解读:
现在,car_2 在初始化时会通过自定义拷贝构造函数,在堆上分配一块全新的 Engine 内存,并将内容填充为初始状态。此后,修改 car_2 的引擎只影响其独立的内存块,car_1 的状态保持不变,且两者的析构过程互不干扰。

总结

C++中的对象拷贝行为高度依赖于类是否包含动态资源。掌握浅拷贝与深拷贝的区别,并能在适当的时候重载拷贝构造函数,是编写健壮、无内存泄漏且安全的C++代码的基础技能。以上就是C++深浅拷贝的详细解析,更多关于C++进阶技巧的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表