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

浅拷贝只是复制指针值,不复制堆内存
当你没写自定义拷贝构造函数时,C++ 默认执行的是浅拷贝:它把 obj1 中的指针成员(比如 int* data)的值(即地址)直接赋给 obj2.data。两个对象的指针指向同一块堆内存。
这会导致后续析构时两次调用 delete 同一地址,触发未定义行为(常见表现是程序崩溃或 double free or corruption 错误)。
典型场景:类中包含 new 出来的资源,比如动态数组、文件句柄、网络连接等。
深拷贝必须在拷贝构造函数和赋值运算符里手动分配新内存
要真正实现资源隔离,必须在拷贝构造函数中用 new 申请新内存,并用 std::copy 或循环把原数据复制过去;同时重载 operator= 防止自我赋值和资源泄漏。
立即学习“C++免费学习笔记(深入)”;
关键点:
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&&))。