std::is_nothrow_move_constructible_v决定vector扩容时用移动还是拷贝:为true则优先移动构造,否则退化为拷贝构造;需显式声明noexcept,否则即使= default也不满足,影响性能与强异常安全性。

std::is_nothrow_move_constructible 在 vector 扩容时决定是否能用移动而非拷贝
当 std::vector 需要扩容(比如调用 push_back 触发重新分配),它会把旧元素搬到新内存。搬的方式取决于元素类型是否满足 std::is_nothrow_move_constructible_v<t></t>:如果为 true,优先调用移动构造;否则退回到拷贝构造——哪怕类型有移动构造函数,只要它没标 noexcept,标准就认为“不安全”,不敢在异常敏感路径(如扩容中途)用它。
这直接关系到性能和异常安全性:
- 移动通常比拷贝快一个数量级(尤其含指针或大缓冲区的类)
- 若移动构造可能抛异常,扩容过程无法保证强异常安全:旧内存已释放、新内存构建一半失败 → 对象处于不可恢复状态
- 编译器不会帮你加
noexcept,必须显式声明
怎么查你的类型是否满足 std::is_nothrow_move_constructible
别靠猜,写个最小验证片段:
#include <type_traits>
#include <vector>
<p>struct Widget {
std::string data;
Widget(Widget&&) = default; // 缺少 noexcept → 不满足
};</p><p>static_assert(!std::is_nothrow_move_constructible_v<Widget>, "Widget must be nothrow move constructible");</p><p><span>立即学习</span>“<a href="https://pan.quark.cn/s/6e7abc4abb9f" style="text-decoration: underline !important; color: blue; font-weight: bolder;" rel="nofollow" target="_blank">C++免费学习笔记(深入)</a>”;</p><p>// 修复:
struct WidgetFixed {
std::string data;
WidgetFixed(WidgetFixed&&) noexcept = default; // ✅
};
static_assert(std::is_nothrow_move_constructible_v<WidgetFixed>, "");
注意几个关键点:
-
= default的移动构造函数默认不是noexcept,即使所有成员都满足 -
std::string的移动构造是noexcept(C++11 起),但组合后仍需显式标注 - 用
static_assert卡在编译期,比运行时崩溃早得多
vector 扩容时的移动/拷贝行为差异实测
以下代码在 GCC 13 + -O2 下可观察到明显耗时差:
struct Heavy { char buf[1024]; Heavy(Heavy&&) noexcept { /* trivial */ } };
struct HeavyCopy { char buf[1024]; HeavyCopy(HeavyCopy&&) { /* same body, but no noexcept */ } };
<p>std::vector<Heavy> v1;
for (int i = 0; i < 100000; ++i) v1.push_back({}); // 全部移动,毫秒级</p><p>std::vector<HeavyCopy> v2;
for (int i = 0; i < 100000; ++i) v2.push_back({}); // 全部拷贝,可能慢 5–10 倍
原因在于:v2 的元素不满足 std::is_nothrow_move_constructible,vector 内部被迫走拷贝分支(见 libstdc++ __uninitialized_move_if_noexcept_a 实现)。即使你手动写 std::move(x),底层策略仍由该 trait 控制。
性能影响不是线性的——数据量越大、单次拷贝越重,差距越明显;某些 STL 实现还会因退化为拷贝而多一次内存清零。
容易被忽略的隐式陷阱:继承与模板推导
即使你写了 noexcept,也可能被覆盖:
- 派生类默认生成的移动构造函数不继承基类的
noexcept属性,必须显式写Derived(Derived&&) noexcept = default; - 模板类中,
T的移动是否noexcept取决于具体实例化类型,例如std::vector<std::unique_ptr<int>>满足,但std::vector<std::shared_ptr<int>>在部分旧标准库中不满足(因控制块原子操作可能抛std::bad_alloc) - 聚合类型(如
struct A { int x; std::string s; };)的默认移动构造函数是否noexcept,取决于所有非静态成员的移动构造是否都noexcept;std::string是,但自定义类型容易漏
真正卡住的往往不是你自己写的类,而是你用的第三方容器嵌套了一层又一层之后,某处 noexcept 断掉了。


















