c++oding="utf-8" ?>
std::is_nothrow_move_constructible_v 决定 vector 扩容时采用移动还是拷贝策略:为 true 时直接移动、高效且强异常安全;为 false 时退化为先拷贝再析构,性能差且弱异常安全。

std::is_nothrow_move_constructible 影响 vector 扩容时的异常安全策略
当 std::vector 需要扩容(比如调用 push_back 触发重新分配),它必须把旧元素搬进新内存。搬的方式取决于元素类型是否能“无异常地移动”——也就是 std::is_nothrow_move_constructible_v<t></t> 是否为 true。
如果为 true,vector 会直接调用移动构造函数,旧对象析构前就完成搬迁,全程不抛异常,且效率高;如果为 false(比如移动构造可能抛异常),vector 就退回到更保守的做法:先在新内存里拷贝构造所有元素,等全部成功后再逐个析构旧元素——这不仅慢,还可能因中途失败导致资源泄漏或状态不一致。
- 关键点不是“能不能移动”,而是“移动会不会抛异常”
- 即使你写了移动构造函数,若没加
noexcept,编译器默认认为它可能抛异常,std::is_nothrow_move_constructible_v仍为false - 自定义类型务必显式声明
T(T&&) noexcept,否则 vector 扩容将被迫走拷贝路径
如何验证你的类型是否满足 nothrow move 构造
别靠猜,用标准 trait 直接查:
static_assert(std::is_nothrow_move_constructible_v<myclass>, "MyClass must be nothrow move constructible"); </myclass>
常见误判场景:
- 成员变量中含非 nothrow 移动类型的(如
std::vector<:string></:string>本身是 nothrow 的,但你自己写的类若含std::mutex或未标记noexcept的成员移动构造,整体就不满足) - 继承了基类,但基类移动构造没加
noexcept,派生类默认也不满足 - 使用了
= default移动构造,但编译器生成的版本是否noexcept取决于所有成员——只要有一个不满足,整个类就不满足
vector 扩容性能差异实测级影响
假设一个 vector<heavyobject></heavyobject> 从容量 1024 扩到 2048,其中 HeavyObject 移动构造耗时约 100ns(含深拷贝资源):
- 满足
nothrow:只做 1024 次移动 + 1024 次析构,总开销 ~200μs - 不满足
nothrow:先做 1024 次拷贝构造(资源双倍占用),再做 1024 次析构(旧对象),若中途第 513 次拷贝失败,前 512 个新对象已构造成功,但旧数据已被清空 → 异常后状态损坏,且实际耗时可能翻倍 - 更隐蔽的问题:某些 STL 实现(如 libstdc++)在 non-nothrow 场景下还会额外做异常防护栈展开,进一步拖慢正常路径
容易被忽略的隐式依赖链
你可能没直接用 std::vector,但间接依赖它的容器或算法一样受此约束:
-
std::unordered_map重哈希时内部桶数组扩容,行为同 vector -
std::stable_sort在需要缓冲区时可能用 vector 临时存数据 - 第三方库中封装的动态数组(如 folly::fbvector)也普遍复用这一判断逻辑
- 甚至
std::optional<t></t>赋值时若T不满足 nothrow 移动,optional的移动赋值也可能退化为拷贝
真正卡住性能和异常安全的,往往不是你写的那行 push_back,而是你忘了给那个小工具类加 noexcept。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











