如何实现深拷贝并掌握拷贝构造函数的高级写法?

2026-05-07 11:441阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计927个文字,预计阅读时间需要4分钟。

如何实现深拷贝并掌握拷贝构造函数的高级写法?

默认的复制构造函数仅执行浅拷贝,只复制对象的类成员,而不会复制指向成员的指针。如果两个对象指向同一块堆内存,析构时重复释放同一内存会导致程序崩溃。

你不能依赖编译器生成的版本,哪怕看起来“能跑”。只要涉及动态分配,就必须显式定义:

class Buffer { char* data_; size_t size_; public: Buffer(const char* s) : size_(strlen(s)) { data_ = new char[size_ + 1]; strcpy(data_, s); } <pre class='brush:php;toolbar:false;'>// 必须写!否则是浅拷贝 Buffer(const Buffer& other) : size_(other.size_) { data_ = new char[size_ + 1]; strcpy(data_, other.data_); } ~Buffer() { delete[] data_; }

};

拷贝构造函数参数必须是 const 引用

写成 Buffer(Buffer other)Buffer(Buffer& other) 都错:
前者会触发无限递归(传值调用又需要拷贝);后者无法绑定临时对象或 const 对象,限制太死。

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

  • Buffer(const Buffer& other) 是唯一安全、通用的签名
  • 即使你不改 other,也必须加 const,否则 func(Buffer b) { Buffer c = b; } 这种常见调用会编译失败
  • 别在函数体内对 other 做非 const 操作(比如调用非常量成员函数),否则说明设计有问题

赋值运算符 ≠ 拷贝构造函数,漏写会导致隐式转换出错

只写了拷贝构造函数,没写 operator=?那 b = a; 仍会调用默认赋值——又是浅拷贝。更危险的是:如果类有 explicit 构造函数,但没禁用拷贝赋值,某些隐式转换可能绕过检查。

  • 拷贝构造函数处理“初始化”:Buffer b = a;func(a)return a;
  • operator= 处理“已有对象被赋值”:b = a;,此时 b 已存在,得先清理旧资源再复制
  • 现代写法推荐“拷贝-交换”惯用法,避免自赋值检查和异常安全问题:
    Buffer& operator=(Buffer other) { swap(*this, other); return *this; }

移动语义出现后,拷贝构造函数依然不能省

C++11 后有了移动构造函数,但编译器不会因为写了 Buffer(Buffer&&) 就自动禁用或优化掉拷贝逻辑。只要代码里出现拷贝场景(比如传 const 引用、容器存值、std::vector::push_back 值插入),还是走拷贝构造函数。

  • 移动构造函数解决的是“临时对象”或“明确 std::move 的对象”,不是替代拷贝
  • 如果你删了拷贝构造函数(比如加了 = delete),而某处又意外触发了拷贝(比如忘记 const& 参数),编译直接报错:use of deleted function ‘Buffer::Buffer(const Buffer&)’
  • 真正想禁拷贝,得同时删拷贝构造 + 拷贝赋值,并接受它只能被移动或引用传递

最常被忽略的一点:深拷贝的“深”不是指多层指针,而是“资源所有权独立”。哪怕只有一级 new,也得自己管;哪怕有 std::unique_ptr,它已经帮你做了深拷贝语义——这时候你反而不该手写拷贝构造函数。

标签:C

本文共计927个文字,预计阅读时间需要4分钟。

如何实现深拷贝并掌握拷贝构造函数的高级写法?

默认的复制构造函数仅执行浅拷贝,只复制对象的类成员,而不会复制指向成员的指针。如果两个对象指向同一块堆内存,析构时重复释放同一内存会导致程序崩溃。

你不能依赖编译器生成的版本,哪怕看起来“能跑”。只要涉及动态分配,就必须显式定义:

class Buffer { char* data_; size_t size_; public: Buffer(const char* s) : size_(strlen(s)) { data_ = new char[size_ + 1]; strcpy(data_, s); } <pre class='brush:php;toolbar:false;'>// 必须写!否则是浅拷贝 Buffer(const Buffer& other) : size_(other.size_) { data_ = new char[size_ + 1]; strcpy(data_, other.data_); } ~Buffer() { delete[] data_; }

};

拷贝构造函数参数必须是 const 引用

写成 Buffer(Buffer other)Buffer(Buffer& other) 都错:
前者会触发无限递归(传值调用又需要拷贝);后者无法绑定临时对象或 const 对象,限制太死。

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

  • Buffer(const Buffer& other) 是唯一安全、通用的签名
  • 即使你不改 other,也必须加 const,否则 func(Buffer b) { Buffer c = b; } 这种常见调用会编译失败
  • 别在函数体内对 other 做非 const 操作(比如调用非常量成员函数),否则说明设计有问题

赋值运算符 ≠ 拷贝构造函数,漏写会导致隐式转换出错

只写了拷贝构造函数,没写 operator=?那 b = a; 仍会调用默认赋值——又是浅拷贝。更危险的是:如果类有 explicit 构造函数,但没禁用拷贝赋值,某些隐式转换可能绕过检查。

  • 拷贝构造函数处理“初始化”:Buffer b = a;func(a)return a;
  • operator= 处理“已有对象被赋值”:b = a;,此时 b 已存在,得先清理旧资源再复制
  • 现代写法推荐“拷贝-交换”惯用法,避免自赋值检查和异常安全问题:
    Buffer& operator=(Buffer other) { swap(*this, other); return *this; }

移动语义出现后,拷贝构造函数依然不能省

C++11 后有了移动构造函数,但编译器不会因为写了 Buffer(Buffer&&) 就自动禁用或优化掉拷贝逻辑。只要代码里出现拷贝场景(比如传 const 引用、容器存值、std::vector::push_back 值插入),还是走拷贝构造函数。

  • 移动构造函数解决的是“临时对象”或“明确 std::move 的对象”,不是替代拷贝
  • 如果你删了拷贝构造函数(比如加了 = delete),而某处又意外触发了拷贝(比如忘记 const& 参数),编译直接报错:use of deleted function ‘Buffer::Buffer(const Buffer&)’
  • 真正想禁拷贝,得同时删拷贝构造 + 拷贝赋值,并接受它只能被移动或引用传递

最常被忽略的一点:深拷贝的“深”不是指多层指针,而是“资源所有权独立”。哪怕只有一级 new,也得自己管;哪怕有 std::unique_ptr,它已经帮你做了深拷贝语义——这时候你反而不该手写拷贝构造函数。

标签:C