如何实现深拷贝并掌握拷贝构造函数的高级写法?
- 内容介绍
- 文章标签
- 相关推荐
本文共计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,它已经帮你做了深拷贝语义——这时候你反而不该手写拷贝构造函数。
本文共计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,它已经帮你做了深拷贝语义——这时候你反而不该手写拷贝构造函数。

