深拷贝必须显式定义拷贝构造函数和赋值运算符;默认行为为浅拷贝,含裸指针时会导致use-after-free;std::vector等标准容器成员可自动深拷贝其内部数据,但裸指针仍需手动处理;移动语义不替代拷贝语义,二者须共存。

深拷贝必须自己写 copy constructor 和 operator=
编译器生成的默认拷贝构造函数和赋值运算符只做浅拷贝——它逐字节复制对象内存,如果类里有指针成员(比如 int*),两个对象会指向同一块堆内存。后续任意一方释放内存,另一方再访问就是 use-after-free,崩溃或数据错乱几乎是必然的。
实操建议:
- 只要类中持有动态分配资源(
new出来的内存、打开的文件描述符、持有的锁等),就必须显式定义copy constructor和operator= - 二者逻辑要一致:都得用
new重新分配内存,并把原对象数据复制过去,而不是直接赋值指针 - 别忘了在
operator=里先检查自赋值:if (this == &other) return *this;,否则delete掉自己内存后就再也读不到源数据了
std::vector 或 std::string 成员能自动深拷贝吗
能,但仅限它们自身管理的那部分内存。比如 class A { std::vector<int> data; };</int>,默认拷贝会调用 std::vector 的拷贝构造函数,它内部已实现深拷贝,所以 data 的元素会被完整复制一份。
注意点:
立即学习“C++免费学习笔记(深入)”;
- 这只是“间接深拷贝”——你没动手写逻辑,但依赖了标准库组件的正确实现
- 如果类里混着裸指针和
std::vector(比如int* raw_ptr;+std::vector<double> vec;</double>),那么vec部分自动深拷贝,raw_ptr部分仍需你手动处理 - 别指望编译器“猜出”你要深拷贝——它永远按成员类型逐个调用其拷贝逻辑,不会跨类型推断语义
移动语义来了,copy constructor 还要写吗
要,而且不能省。移动构造函数(A(A&&))和拷贝构造函数(A(const A&))是两套独立逻辑:前者接管资源,后者必须保证原对象仍可用。
常见错误现象:
- 只写了移动构造,删掉拷贝构造,结果传 const 引用参数时编译失败(
error: use of deleted function) - 把移动构造体内的
other.ptr = nullptr;错抄进拷贝构造里,导致原对象指针被清空,违反拷贝语义 - 忘记在移动赋值中释放当前对象已有资源,造成内存泄漏
性能影响:深拷贝本身开销大,但这是语义必需;如果真想避免,应从设计上减少拷贝场景(比如用引用/指针传递,或改用移动),而不是绕过拷贝控制。
调试深拷贝问题最有效的办法
加日志,而不是靠猜。在每个关键函数里打印地址和内容,比如:
A::A(const A& other) : ptr(new int(*other.ptr)) {
std::cout << "copy ctor: this=" << this << " ptr=" << ptr << " *ptr=" << *ptr << "\n";
}
这样一眼就能看出两个对象的 ptr 是否指向不同地址。比反复看 core dump 或 valgrind 报告快得多。
容易被忽略的地方:析构函数里的 delete ptr; 必须和拷贝构造中 new 的次数严格匹配。少一次 delete 是泄漏,多一次是 double-free——而 double-free 往往不立刻崩溃,可能拖到后续某次 new 或其他操作才暴露。









