std::vector扩容时若移动构造函数未显式声明noexcept,std::is_nothrow_move_constructible_v<T>必为false,强制退化为深拷贝,导致内存峰值翻倍、时间开销升至O(n)并破坏强异常安全。

不加 noexcept 的移动构造函数,哪怕只有一行赋值,std::is_nothrow_move_constructible_v<T> 也会返回 false;std::vector 扩容时直接退化为深拷贝——这不是慢一点,是性能断崖下跌。
为什么 std::vector 扩容必须依赖这个 trait
扩容本质是三步:分配新内存 → 搬移旧元素 → 析构旧对象。若搬移(即移动构造)可能抛异常,就无法保证强异常安全:中途失败会导致部分元素已搬、部分未搬、旧内存已被释放,状态彻底损坏。
标准库因此强制分叉,且判断在编译期完成,无运行时开销:
- 若
std::is_nothrow_move_constructible_v<T>为true:调用T(T&&)移动每个元素,再析构原对象;开销接近 O(1) - 若为
false:退回到“先拷贝、全成功后再析构”策略;触发多次堆分配、memcpy、析构链式调用
如何让自定义类真正满足该 trait
不是“写了移动构造函数”就行,关键是让编译器静态确认它绝不会抛异常。以下缺一不可:
立即学习“C++免费学习笔记(深入)”;
- 移动构造函数声明末尾必须显式写
noexcept,不能省略,不能写成noexcept(true)或空括号 - 所有数据成员的移动构造函数也必须满足
std::is_nothrow_move_constructible_v(例如std::string在 C++11+ 主流实现中满足,但std::pmr::string可能不满足) - 基类的移动构造函数同样需为
noexcept;派生类即使手动写了移动构造,若基类没标,整个 trait 仍为false - 含
std::mutex、std::thread或未标记noexcept的自定义 RAII 成员?该类天然不满足,硬标noexcept是未定义行为
怎么验证和卡住问题在编译期
别等上线后发现 push_back 变慢十倍。把校验放在类定义后或容器使用前:
static_assert(std::is_nothrow_move_constructible_v<Widget>, "Widget must be nothrow move constructible");
注意:std::vector 插入末尾时还可能用到移动赋值,所以 std::is_nothrow_move_assignable_v<T> 也得一起检查:
static_assert(std::is_nothrow_move_assignable_v<Widget>, "Widget must be nothrow move assignable");
常见错误是只查构造、漏查赋值;还有继承场景下,基类没标 noexcept,派生类即使写了也白搭——trait 不会自动传播。
容易被忽略的复杂点
第三方类型(如某些 JSON 库的 value 类)未必标记 noexcept,要用前必须查文档或 static_assert;std::function 存储的可调用对象,其移动是否 noexcept 取决于所存对象本身,极易被忽略;debug 模式下某些 STL 实现可能弱化检查,但 release 下严格依赖——性能问题往往只在上线后暴露。



















