std::vector扩容时若元素移动构造非noexcept,编译器会插入异常安全回滚逻辑拖慢性能;需用-fsanitize=undefined检测__cxa_throw、显式声明noexcept、static_assert校验,并确保所有成员及调用路径均满足nothrow要求。

当 std::vector 在扩容时反复移动元素,若元素类型不满足 noexcept 移动构造,编译器必须为每次移动插入异常安全的回滚逻辑,这会显著拖慢 reallocate 速度——你得知道怎么验证它是否生效、为什么失效、以及如何强制启用无异常路径。
确认容器扩容时是否触发了异常安全开销
用 -fsanitize=undefined 编译并运行一段强制扩容的代码,观察是否有 __cxa_throw 调用栈;若存在,说明编译器未将移动构造视为 noexcept,正在生成保护性析构回滚代码。
在类定义中显式添加 noexcept 限定符:struct Widget { Widget(Widget&&) noexcept; }; 否则即使移动构造体内部没抛异常,std::is_nothrow_move_constructible<Widget>::value 仍为 false。
用 static_assert 验证:static_assert(std::is_nothrow_move_constructible_v<Widget>, "Widget must be nothrow move constructible"); 编译失败就说明移动构造函数未正确声明 noexcept,或其调用的成员移动构造本身也不 noexcept。
立即学习“C++免费学习笔记(深入)”;
std::vector 扩容路径中移动策略的实际分支
方法一:编译器内建优化路径
当 T 满足 std::is_nothrow_move_constructible_v<T> && std::is_nothrow_destructible_v<T>,libstdc++ 和 libc++ 均直接使用 memcpy 或 unroll 循环移动,跳过单个元素的 try-catch 包裹。
方法二:退化为复制构造路径
若移动构造非 noexcept 但复制构造是 noexcept,部分标准库实现(如旧版 libstdc++)会降级使用 copy + destroy,此时扩容性能可能比预期更差——因为复制比移动开销更大,且仍需保证强异常安全。
方法三:强制抛异常触发回滚
在移动构造函数里 throw std::runtime_error("boom") 并关闭 noexcept,用 valgrind --tool=callgrind 观察 _M_reallocate 的调用深度;你会看到额外的 __cxxabiv1::__terminate 调用链,证明异常清理逻辑已注入。
让自定义类型真正通过 nothrow 移动校验
第一步:检查所有成员变量的移动构造是否都标记了 noexcept。哪怕一个 std::string 成员没被 noexcept 修饰,整个类就自动失败。
第二步:若含 std::unique_ptr 成员,确保其指向类型 T 的析构函数不抛异常——因为 std::unique_ptr<T> 的移动构造要求 T 的析构函数是 noexcept(C++11 起),否则 unique_ptr 自身移动构造就不是 noexcept。
第三步:禁止在移动构造函数中调用任何未声明 noexcept 的函数,包括 operator new、自定义分配器的 construct 方法、甚至 std::cout << ——这些都可能隐式抛出 std::bad_alloc 或其他异常。【只要任意一条调用路径存在抛异常可能,std::is_nothrow_move_constructible 就返回 false】
第四步:对聚合类型,用 = default 生成移动构造时,编译器会自动推导 noexcept 属性;但一旦你手写定义,就必须显式加 noexcept,否则默认按可能抛异常处理。


















