必须显式定义拷贝构造函数和赋值运算符以避免浅拷贝引发 double free;应实现深拷贝、自赋值检查及移动语义;优先使用 raii 和智能指针;遵循 rule of five 检查清单。

拷贝构造函数和赋值运算符必须显式定义
当类管理动态内存(比如 new 出来的数组)、文件句柄、socket 描述符等内部资源时,编译器自动生成的拷贝构造函数和 operator= 只做浅拷贝——即复制指针值而非所指内容。这会导致两个对象指向同一块内存,析构时重复释放,触发 double free 或 use-after-free。
解决方式是手动实现深拷贝逻辑:
- 在拷贝构造函数中,为新对象分配独立内存,并逐字节或按语义复制原始数据
- 在
operator=中,先检查自赋值(if (this == &other)),再释放当前资源,再分配并复制 - 务必同步更新析构函数,确保资源被正确释放
移动语义能避免不必要的深拷贝
如果对象后续不再使用(如函数返回临时对象、容器插入右值),用移动构造/赋值比深拷贝更高效。关键在于把源对象的资源“偷”过来,再将其内部指针置为 nullptr,避免析构时误删。
典型写法:
MyClass(MyClass&& other) noexcept : data_(other.data_), size_(other.size_) {
other.data_ = nullptr;
other.size_ = 0;
}
注意点:
- 移动操作应标记
noexcept,否则std::vector等容器扩容时可能退回到拷贝路径 - 移动后原对象仍需处于可析构状态(比如指针设为
nullptr) - 不要对已移动的对象调用非平凡成员函数,除非明确文档保证
RAII + 智能指针可大幅简化资源管理
手动管理 new/delete 容易出错。优先用 RAII 封装资源,例如:
- 用
std::unique_ptr替代裸指针:默认禁用拷贝,只允许移动,天然防止浅拷贝误用 - 用
std::shared_ptr表达共享所有权:拷贝时引用计数+1,无需手写深拷贝逻辑 - 对非内存资源(如文件),封装成类似
FileHandle的 RAII 类,构造时打开,析构时关闭
这样,只要类成员都是 RAII 类型,你甚至可以删除自定义拷贝/移动函数,依赖编译器生成的版本即可安全工作。
Rule of Five 是底线检查清单
一旦类需要自定义析构函数(因为要释放资源),就该立刻检查是否也需要以下五个特殊成员函数:
-
~MyClass()(已写) -
MyClass(const MyClass&)(拷贝构造) -
MyClass& operator=(const MyClass&)(拷贝赋值) -
MyClass(MyClass&&)(移动构造) -
MyClass& operator=(MyClass&&)(移动赋值)
漏掉任意一个都可能引发未定义行为。现代 C++ 中,如果不需要拷贝,显式删除:MyClass(const MyClass&) = delete;,比留着编译器默认版本更安全。
真正容易被忽略的是:移动操作后原对象的状态合法性,以及自赋值在拷贝赋值中的防御必要性——这两处不加检查,问题往往在线上运行很久才暴露。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











