std::is_nothrow_move_constructible 是标准库启用移动语义的编译期许可证;为 false 时 vector 扩容退化为拷贝以保证强异常安全,需通过 noexcept 修复移动构造并逐层验证成员。

std::is_nothrow_move_constructible 不是性能“开关”,而是标准库决定“敢不敢用移动语义”的编译期许可证——没它,std::vector::resize 就会绕开移动,退回去拷贝。
std::vector 扩容时到底看谁的脸色
容器扩容(比如 resize 或 push_back 触发重分配)必须保证强异常安全:要么全成功,要么状态回滚。如果移动构造可能抛异常,部分元素被移动、部分没动,就无法安全回滚。
所以 std::vector 会查 std::is_nothrow_move_constructible_v<t></t>:
- 为 true:直接在新内存上调用移动构造,旧对象析构——快,且安全
- 为 false:改用“拷贝构造 + 旧对象析构”——慢,但能保证:若拷贝中途抛异常,旧内存仍完整可用
这不是优化建议,是标准强制行为。你写了个 Buffer 类,移动构造没加 noexcept,哪怕函数体里连 throw 都没有,std::vector<buffer></buffer> 扩容照样走拷贝路径。
立即学习“C++免费学习笔记(深入)”;
为什么 is_nothrow_move_constructible_v 为 false 的常见原因
它不运行代码,只检查签名和继承链。以下任一条件成立,结果就是 false:
- 显式定义了移动构造,但没写
noexcept(哪怕函数体空) - 没定义移动构造,而默认生成的版本因成员/基类不满足 nothrow 被禁用(例如某个成员的移动构造不是
noexcept) - 类型删除了移动构造(
Buffer(Buffer&&) = delete;),此时回退检查拷贝构造——若拷贝构造也不是noexcept,结果仍是false - 某个基类或非静态成员的移动构造不满足
noexcept,整个类型就被“污染”
典型陷阱:std::string 在多数实现中是 noexcept 移动的,但如果你的类里嵌了一个自定义类 Logger,而 Logger 的移动构造调用了可能抛异常的日志 API(哪怕你没标 noexcept(false),只要没标 noexcept,编译器就推导为可能抛),那整个类就“掉出”高效路径。
如何验证并修复你的类型
别猜,用 static_assert 在编译期卡住:
static_assert(std::is_nothrow_move_constructible_v<Buffer>, "Buffer must be nothrow move constructible for vector efficiency");
修复动作很明确:
- 给移动构造函数加上
noexcept(不是noexcept(true),后者冗余) - 确保所有成员和基类也都满足——逐层检查,重点盯住
std::unique_ptr、std::vector等标准类型(它们通常满足),以及你自己写的资源管理类 - 如果某个成员确实无法做到
noexcept移动(比如封装了系统调用),考虑用std::shared_ptr包一层,或重构为 PIMPL,把“危险”成员隔离到堆上
注意:noexcept 是承诺,不是魔法。你加了 noexcept 却在函数体里写了 throw,程序会调用 std::terminate——这比慢更糟。
和 std::is_nothrow_swappable 的关系不能忽略
std::swap 默认实现是三步移动(move-assign 风格),所以它也依赖移动操作的 noexcept 性。如果 std::is_nothrow_swappable_v<t></t> 是 false,往往意味着 std::is_nothrow_move_constructible_v<t></t> 或 std::is_nothrow_move_assignable_v<t></t> 至少有一个是 false。
某些泛型算法(如 std::make_heap)内部会调用 swap,也会因此降级。所以别只盯着移动构造——检查完整链条:move_constructible、move_assignable、swappable,三者都 noexcept,才算真正“通关”。


















