c++oding="utf-8" ?>
vector扩容走移动路径的前提是std::is_nothrow_move_constructible_v为true;否则退化为拷贝+析构的慢路径,以保障强异常安全。

vector 扩容时是否走移动路径,不取决于你有没有写移动构造函数,而取决于 std::is_nothrow_move_constructible_v 是否为 true。只要它是 false,哪怕移动构造函数体里只有一行 data_ = nullptr,vector 也会退回到拷贝+析构的慢路径。
为什么 vector 扩容必须检查 noexcept 移动构造
扩容本质是内存重分配:申请新块、搬元素、释放旧块。若搬的过程(即移动构造)可能抛异常,就无法保证“全搬完或原地不动”的强异常安全——中途失败会导致部分元素已搬、部分未搬、旧内存已被释放,状态彻底损坏。
因此标准强制要求:只有确认移动绝不会抛异常(noexcept),才允许直接移动;否则必须先拷贝全部到新内存,等拷贝全成功后再销毁旧对象。这多了一轮内存分配和深拷贝开销,且异常发生时仍可能泄漏资源。
- 即使你手动写了
T(T&&),没加noexcept→ trait 为false - 写了
T(T&&) noexcept,但某个成员的移动构造不是noexcept→ trait 仍为false - 基类移动构造没标
noexcept,派生类即使写了也无效
如何验证你的类型是否真正满足 nothrow 移动构造
别靠编译不报错或日志猜测。最可靠的是编译期断言:
static_assert(std::is_nothrow_move_constructible_v<myclass>, "MyClass must be nothrow move constructible");</myclass>
配合最小可测类快速定位问题:
- 先写一个空移动构造:
MyClass(MyClass&&) {}→ assert 失败 - 加上
noexcept:MyClass(MyClass&&) noexcept {}→ assert 应通过 - 再逐个添加成员(如
std::string、std::vector),观察 assert 是否仍通过——任一成员不满足,整体就失败
注意:std::string 在 C++17 起默认是 noexcept 移动构造,但若用了自定义分配器(如 std::pmr::string),可能破坏该属性。
常见踩坑点:noexcept 不是装饰,是契约
noexcept 声明不是可有可无的标记,它是一份运行期承诺。一旦函数体内调用可能抛异常的操作(如 new、std::mutex::lock()、未标记 noexcept 的成员移动),程序会在异常发生时直接调用 std::terminate(),而非回退到拷贝路径。
- 移动构造中调用
other.data_.clear()?先确认clear()是否noexcept - 含
std::thread或std::mutex成员?它们不可移动或移动可能抛异常 → 整个类is_nothrow_move_constructible_v必为false - 用
= default?写成MyClass(MyClass&&) noexcept = default;,不能省略noexcept
更隐蔽的是:某些 STL 实现(如 libstdc++)在 noexcept 满足且类型平凡可析构时,会进一步启用 memmove 位拷贝优化——这比逐个调用移动构造快数倍。但这个优化链的第一环,就是 std::is_nothrow_move_constructible_v。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











