深拷贝必须手动实现,因默认拷贝构造函数仅浅拷贝指针值,导致野指针或重复释放;需显式定义const引用参数的拷贝构造函数及赋值运算符,并推荐用RAII容器替代裸指针。

深拷贝必须手动实现,编译器默认的拷贝构造函数只做浅拷贝
当你类中持有 new 出来的指针、malloc 分配的内存,或封装了类似 FILE*、int fd 这类资源句柄时,编译器自动生成的拷贝构造函数只会复制指针值(即地址),不会复制它指向的内容。两个对象最终指向同一块内存——一旦其中一个析构时 delete 了,另一个再访问就是野指针;若都析构,还会触发重复释放(double free)。
解决方式只有一个:显式定义拷贝构造函数,并在其中用 new(或 malloc)为新对象分配独立内存,再逐字节或按逻辑拷贝原始数据。
常见错误示例:
class Buffer {
public:
char* data;
size_t size;
Buffer(size_t s) : size(s) { data = new char[s]; }
// ❌ 缺失拷贝构造函数 → 默认浅拷贝
~Buffer() { delete[] data; }
};正确写法需补上:
立即学习“C++免费学习笔记(深入)”;
Buffer(const Buffer& other) : size(other.size) {
data = new char[size];
std::copy(other.data, other.data + size, data);
}拷贝构造函数参数必须是 const 引用,否则会无限递归
如果写成 Buffer(Buffer other) 或 Buffer(Buffer& other),前者传参会触发拷贝构造本身(还没进函数体就又调一次),后者虽不递归但无法绑定临时对象或 const 对象,实用性极差。
必须使用 const Buffer& ——既避免拷贝开销,又保证能接收所有合法右值/左值。
容易踩的坑:
-
Buffer(Buffer& other):无法初始化Buffer b = get_temp_buffer();(临时对象不能绑定非常量引用) -
Buffer(Buffer other):编译可能通过,但运行时栈溢出或崩溃(无限调用自身) - 漏掉
const还可能让std::vector的push_back失败(内部移动/拷贝逻辑依赖 const 引用)
赋值运算符 ≠ 拷贝构造函数,二者必须同时重载
拷贝构造函数只在对象“诞生时”被调用(如 Buffer b1 = b2;、函数传参、返回局部对象),而赋值操作(b1 = b2;)走的是 operator=。如果只写了拷贝构造却没写赋值运算符,后者仍用默认的位拷贝,一样引发浅拷贝问题。
标准写法建议采用“拷贝-交换”惯用法(copy-and-swap),自动处理自我赋值和异常安全:
Buffer& operator=(Buffer other) { // 注意:参数按值传入 → 触发拷贝构造
swap(*this, other);
return *this;
}
friend void swap(Buffer& a, Buffer& b) noexcept {
using std::swap;
swap(a.data, b.data);
swap(a.size, b.size);
}关键点:
- 参数按值传入,天然复用已写好的拷贝构造函数
- 无需手动检查
this == &other,swap 后 olddata在临时对象析构时自动释放 - 若拷贝过程抛异常,原对象状态不受影响(强异常安全)
现代 C++ 更推荐用 RAII 容器替代裸指针,从源头规避问题
手动管理内存是深/浅拷贝问题的根源。C++11 起,优先用 std::vector、std::string、std::unique_ptr 等 RAII 类型,它们自带正确的拷贝/移动语义。
比如把上面的 Buffer 改成:
class Buffer {
std::vector data;
size_t size;
public:
Buffer(size_t s) : size(s), data(s) {}
// ✅ 无需写拷贝构造、赋值、析构 —— vector 全替你做了
}; 此时 Buffer b1 = b2; 自动执行深拷贝,且无泄漏、无重复释放风险。只有当你确实需要精细控制内存布局、或对接 C API(如 read(fd, buf, len))时,才考虑裸指针 + 手动深拷贝。
真正难的不是写对拷贝逻辑,而是判断「什么时候非得自己管内存」——多数业务代码里,这个“时候”其实并不存在。








