深拷贝必须手动实现copy constructor和operator=,且需与析构函数协同:分配新内存、深拷贝内容、检查自赋值、释放旧资源、避免悬空指针,并用工具验证。

深拷贝必须手动写 copy constructor 和 operator=
编译器生成的默认拷贝是浅拷贝,只要类里有指针成员(比如 int*、std::string* 或裸指针管理的资源),直接赋值或传参就会出问题——两个对象指向同一块内存,一个析构后另一访问就 use-after-free 或 double free。
实操建议:
- 只要类里有动态分配的资源(堆内存、文件句柄、socket 等),就必须显式定义
copy constructor和operator= - 两者逻辑要一致:都得用
new(或malloc)重新分配内存,再逐字节或按语义拷贝内容 - 别忘了在
operator=开头检查自赋值:if (this == &other) return *this; - 如果用了
std::unique_ptr这类 RAII 类型,它自己会阻止拷贝,这时要么禁用拷贝(= delete),要么改用std::shared_ptr(共享语义,不是深拷贝)
不写 ~MyClass() 就别想安全深拷贝
深拷贝和析构是一体两面。拷贝时新分配了内存,析构时就必须释放它;否则就算拷贝逻辑全对,也会内存泄漏。
常见错误现象:
立即学习“C++免费学习笔记(深入)”;
- 程序跑一会儿就变慢、OOM —— 深拷贝反复发生,但
~MyClass()没写或没释放指针成员 - 只写了
copy constructor,但operator=里没delete原来的指针,导致内存泄漏 + 悬空指针
实操建议:
-
~MyClass()必须释放所有由本对象独占的堆内存(对应new的地方) - 如果类里有多个指针成员,每个都要判空再
delete,或统一初始化为nullptr - 用
valgrind --leak-check=full ./a.out验证有没有漏掉释放
std::vector 和 std::string 会自动深拷贝,但别误以为“不用管”
它们内部确实做了深拷贝,但前提是:你没绕过封装直接操作其内部缓冲区(比如取 .data() 后存裸指针)。
使用场景与坑:
- 如果你的类只含
std::vector<int></int>、std::string成员,默认拷贝完全安全,不用手写copy constructor - 但一旦你写了
int* raw_data_ = vec.data();并保存这个指针,后续拷贝时raw_data_就变成悬空指针——因为新对象的vec是独立副本,地址不同 - 同理,
std::string的c_str()在拷贝后可能失效,不能长期持有
移动语义(C++11+)让深拷贝更难写对,也更容易优化错
加了 move constructor 和 move operator= 后,原对象进入“有效但未指定状态”,此时它的指针成员通常被置为 nullptr。如果深拷贝逻辑和移动逻辑混在一起,容易漏掉判空,导致 delete nullptr 虽不崩,但后续 new 失败或逻辑错乱。
实操建议:
- 移动函数里把源对象指针设为
nullptr,并在所有析构/赋值前加if (ptr_) delete ptr_; - 不要在
copy constructor里调用std::move,那是反模式 - 用
clang++ -fsanitize=address,undefined编译运行,能快速暴露移动后访问已转移资源的问题
真正麻烦的从来不是“怎么写深拷贝”,而是“怎么确保每次新增成员变量时,拷贝、赋值、移动、析构四套逻辑依然同步更新”。漏掉任意一个环节,bug 就藏在偶发的内存踩踏或静默泄漏里。








