必须手动定义~myclass()当类持有裸指针、文件描述符或socket句柄等需显式释放的资源时,否则将导致内存泄漏或句柄耗尽;且一旦定义析构函数,就应整体审视五法则。

什么时候必须手动定义 ~MyClass()?
当你类里持有裸指针、文件描述符、socket 句柄等需要显式释放的资源时,编译器自动生成的析构函数不会帮你关文件或 delete 内存,这时必须自己写 ~MyClass()。不写,大概率内存泄漏或句柄耗尽;写了但没配齐其他四法则,移动后原对象可能二次析构——比如 delete 同一块内存两次。
常见错误现象:double free or corruption、程序在 std::vector::push_back() 时崩溃、移动后访问野指针。
- 典型场景:封装
char*缓冲区、FILE*、int fd - 只要析构函数不是空的(即做了资源清理),五法则就该被整体审视
- 如果用
std::unique_ptr或std::shared_ptr替代裸指针,析构函数可省,五法则自动退化为“零法则”
std::move() 不会触发移动构造,为什么?
因为移动构造函数本身没被声明或被隐式删除了。编译器只在“没有用户声明的拷贝/移动操作,且所有成员都可移动”时,才自动生成移动构造函数。一旦你写了拷贝构造、拷贝赋值、析构函数中的任意一个,移动操作就不再自动生成(C++11/14 行为)。
使用场景:想把临时对象高效转移进容器,结果发现还是调了拷贝构造,性能掉一截。
立即学习“C++免费学习笔记(深入)”;
- 检查是否误删了
MyClass(MyClass&&)声明,或加了= delete - 若声明了
MyClass(const MyClass&)但没声明MyClass(MyClass&&),std::move(x)的结果类型虽是右值引用,却找不到匹配的构造函数,退而求其次调用拷贝构造 - C++17 后部分场景有强制拷贝消除(RVO/NRVO),但不能依赖它替代正确的移动语义
移动赋值运算符里为什么要检查自赋值?
要,但仅当你的移动赋值不是“先清理再接管”模式时才真需要。标准写法 if (this != &other) { /* ... */ } 在移动语义中其实是冗余甚至有害的——移动操作本就预期源对象进入有效但未指定状态,自赋值本身合法且无害(比如 std::vector 的移动赋值允许自赋值)。
容易踩的坑:有人照搬拷贝赋值的自检逻辑,在移动赋值里清空 this 成员后再 move other,结果 this == &other 时把自己清空又试图从空状态 move,直接 UB。
- 正确做法:移动赋值应无条件接管资源,然后把
other置为安全空状态(如指针设为nullptr) - 自赋值检查只在“清理动作不可逆”时有意义(例如先
delete ptr再ptr = other.ptr),但这种写法本身已违背移动语义初衷 - 更安全的惯用法:
std::swap(*this, other);—— swap 天然支持自赋值,且简洁
为什么 default 关键字不能乱用?
因为 = default 生成的函数未必符合你的资源管理逻辑。比如你写了自定义析构函数,又对移动构造写 MyClass(MyClass&&) = default;,编译器会按成员逐个移动——但如果某个成员是 std::unique_ptr,它可移动;另一个是裸指针,它不可移动(默认行为是拷贝),此时 = default 会静默失败或调用拷贝,导致意外行为。
性能影响:= default 在类含不可移动成员时,可能让移动操作退化为深拷贝,且编译器不报错。
- 只有当所有成员都满足移动语义要求时,
= default才真正“免费”且安全 - 若类中有裸指针或 FILE*,别偷懒,老老实实写移动构造和移动赋值,把资源指针置
nullptr - 用
static_assert(std::is_move_constructible_v<myclass>)</myclass>在编译期验证,比运行时出错更早发现问题
移动语义不是加几个 && 就完事的。最常被忽略的是:移动后源对象的状态必须明确且安全,哪怕只是 ptr = nullptr 这一行。否则,析构函数里再碰一次那个指针,就是终点。










