浅拷贝仅复制指针值导致双释放,深拷贝需在拷贝构造函数和赋值运算符中手动分配内存并复制数据;现代C++推荐用std::vector等RAII容器替代裸指针,必要时须完整实现Rule of Five。

浅拷贝只是复制指针值,不复制堆内存
当你没写自定义拷贝构造函数时,C++ 默认执行的是浅拷贝:它把 obj1 中的指针成员(比如 int* data)的值(即地址)直接赋给 obj2.data。两个对象的指针指向同一块堆内存。
这会导致后续析构时两次调用 delete 同一地址,触发未定义行为(常见表现是程序崩溃或 double free or corruption 错误)。
典型场景:类中包含 new 出来的资源,比如动态数组、文件句柄、网络连接等。
深拷贝必须在拷贝构造函数和赋值运算符里手动分配新内存
要真正实现资源隔离,必须在拷贝构造函数中用 new 申请新内存,并用 std::copy 或循环把原数据复制过去;同时重载 operator= 防止自我赋值和资源泄漏。
立即学习“C++免费学习笔记(深入)”;
关键点:
- 拷贝构造函数参数必须是
const T&,避免递归调用 - 赋值运算符要先检查
this == &rhs - 旧资源必须先释放再 new,否则内存泄漏
- 记得同步更新所有相关成员(比如长度字段
size)
class Buffer {
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
Buffer(const Buffer& other) : size(other.size), data(new int[other.size]) {
std::copy(other.data, other.data + size, data);
}
Buffer& operator=(const Buffer& other) {
if (this == &other) return *this;
delete[] data;
size = other.size;
data = new int[other.size];
std::copy(other.data, other.data + size, data);
return *this;
}
~Buffer() { delete[] data; }
private:
size_t size;
int* data;
};现代 C++ 更推荐用 RAII 容器替代裸指针
手写深拷贝容易出错,且不符合“零成本抽象”原则。绝大多数情况下,应该用 std::vector、std::string、std::unique_ptr 等——它们内部已正确实现深拷贝语义(或移动语义)。
例如把 int* data 换成 std::vector,就不再需要自己写拷贝构造函数,编译器生成的默认版本就能安全工作。
例外情况:封装底层系统资源(如 GPU buffer、自定义内存池)时,仍需手动管理,此时必须严格遵循 Rule of Three / Rule of Five。
容易被忽略的:移动语义会绕过深拷贝逻辑
如果只实现了深拷贝但没实现移动构造/赋值,C++11 及以后可能因隐式移动(比如返回局部对象)而调用默认移动函数——它其实是浅搬指针,接着析构源对象时就会释放资源,导致目标对象悬空。
所以一旦你写了自定义析构函数或拷贝控制成员,就必须显式声明移动操作(或 = default),否则可能引发静默错误。
最稳妥的做法:要么全用标准容器,要么完整实现 Rule of Five(~T(), T(const T&), T(T&&), operator=(const T&), operator=(T&&))。









