c++如何实现深拷贝与浅拷贝_c++拷贝构造函数详解【原理】

2次阅读

浅拷贝是编译器自动生成的逐字节复制,导致指针成员共享同一内存,引发double free等问题;深拷贝需手动实现拷贝构造函数,为指针重新分配并复制数据;移动语义可避免开销,优先用于临时对象

c++如何实现深拷贝与浅拷贝_c++拷贝构造函数详解【原理】

浅拷贝就是默认的拷贝行为,但会出问题

当你没写拷贝构造函数时,c++ 编译器自动生成一个——它逐字节复制对象内存,也就是浅拷贝。对只含基本类型或不带资源指针的类,这没问题;但一旦类里有 int*char* 或其他动态分配的资源,浅拷贝会让两个对象指向同一块内存。

常见错误现象:double free or corruption、程序崩溃、值被意外覆盖。比如一个 String 类用 char*字符串,浅拷贝后两个对象析构时都调用 delete[] ptr,第二次 delete 就崩了。

  • 浅拷贝本质是位拷贝(bitwise copy),不调用任何构造函数
  • 所有成员变量按声明顺序依次复制,包括指针值本身(而非它指向的内容)
  • 只要类中没有显式定义拷贝构造函数,就一定发生浅拷贝

深拷贝必须手动实现拷贝构造函数

深拷贝的核心是:为每个指针成员重新分配内存,并把原对象的数据完整复制过去。这意味着你必须自己写拷贝构造函数,且通常要配合重载赋值运算符 operator=析构函数(三法则/五法则)。

示例关键逻辑:

立即学习C++免费学习笔记(深入)”;

class String {     char* data;     size_t len; public:     String(const char* s) : len(s ? strlen(s) : 0) {         data = new char[len + 1];         if (s) strcpy(data, s);     } 
// 深拷贝构造函数 String(const String& other) : len(other.len) {     data = new char[len + 1];  // 新分配     if (other.data) strcpy(data, other.data); // 复制内容 }  ~String() { delete[] data; }

};

  • 必须检查源对象指针是否为空,避免 strcpy(nullptr)
  • 分配内存失败时应抛异常(如 std::bad_alloc),否则可能造成部分初始化对象
  • 若类中有多个指针成员,每个都要单独 new + memcpy / copy

拷贝构造函数被调用的 4 种典型场景

很多人以为只在 String s2 = s1; 时触发,其实还有更隐蔽的调用点,容易漏掉深拷贝逻辑导致 bug

  • 用一个已存在对象初始化新对象:String s2(s1);String s2 = s1;
  • 函数传参时以值传递方式接收对象:void func(String s) { ... } → 调用拷贝构造创建形参
  • 函数返回局部对象(非引用):String create() { return String("hi"); } → 可能触发(即使有 RVO 优化,语义上仍要求可拷贝)
  • 用对象初始化容器元素(如 std::vector v(10, s1);)→ 拷贝构造被调用 10 次

这些地方一旦用了浅拷贝,默认行为不会报错,但后续析构或修改会暴露问题。

现代 C++ 更推荐移动语义替代深拷贝

深拷贝开销大,尤其数据量大时。C++11 后,如果对象临时生成或明确不再需要原值,应该优先用移动构造函数把资源“偷过来”,而不是复制。

例如补充移动构造函数:

String(String&& other) noexcept : data(other.data), len(other.len) {     other.data = nullptr;     other.len = 0; }
  • 移动构造函数参数是右值引用 T&&,且最好加 noexcept
  • 移动后原对象必须处于有效但未指定状态(比如指针置空),确保析构安全
  • 容器扩容、std::make_unique、函数返回临时对象等场景,编译器会优先选移动而非拷贝

真正难的不是写深拷贝,而是判断什么时候该深拷贝、什么时候该移动、什么时候根本不该拷贝(改用智能指针或值语义封装)。资源管理边界模糊时,bug 往往藏在析构和异常路径里。

text=ZqhQzanResources