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