std::vector扩容时若元素类型满足std::is_nothrow_move_constructible_v且移动赋值也noexcept,则用“移动+析构”替代拷贝;否则强制深拷贝。

容器扩容时,std::is_nothrow_move_constructible 决定是否能用“移动+析构”替代“拷贝”,否则强制降级为深拷贝——这是性能分水岭。
std::vector 扩容时如何触发移动优化
当 std::vector 容量不足需重新分配内存,它会尝试将旧元素“搬”到新内存。若元素类型满足 std::is_nothrow_move_constructible_v 且移动赋值也 noexcept,标准允许直接调用移动构造(后析构原对象);否则必须用拷贝构造——哪怕你写了移动函数但没加 noexcept,编译器也不敢用。
- 关键条件是移动构造函数声明为
noexcept(不是“不抛异常”,而是显式标注) -
std::vector不检查你实际会不会抛异常,只查类型 trait 和函数签名 - 常见陷阱:自定义类写了
T(T&&)却漏了noexcept,导致 vector 扩容全程拷贝
怎么验证你的类型是否被 vector 当作可无异常移动
别靠猜,直接测:
#include <type_traits>
#include <vector>
struct Foo {
Foo() = default;
Foo(Foo&&) noexcept {} // ✅ 必须有 noexcept
Foo& operator=(Foo&&) noexcept { return *this; }
};
static_assert(std::is_nothrow_move_constructible_v<foo>); // 通过
static_assert(std::is_nothrow_move_assignable_v<foo>); // 通过
std::vector<foo> v(1000);
v.reserve(2000); // 此时若 trait 失败,内部会走拷贝分支
</foo></foo></foo></vector></type_traits>
- 仅检查
std::is_nothrow_move_constructible_v不够,std::is_nothrow_move_assignable_v也要成立(vector 插入末尾时可能用到移动赋值) - 用
static_assert在编译期卡住,比运行时性能掉坑更早发现问题 - 注意:基类/成员若有非
noexcept移动操作,会传导导致整个类 trait 为 false
move-only 类型(如 std::unique_ptr)为什么天然安全
std::unique_ptr 的移动构造和赋值都带 noexcept,所以 vector 扩容时永远走移动路径,不会意外退化。
- 对比
std::shared_ptr:它的移动构造也是noexcept,同样受益 - 但自定义 RAII 类(如文件句柄、socket 包装器)常忽略
noexcept,结果在容器里变成性能黑洞 - 第三方库类型(如某些 JSON 解析器的 value 类)未必标记
noexcept,要用前先查文档或static_assert
真正容易被忽略的是:即使你把所有移动操作都标了 noexcept,只要有一个成员变量的移动构造不是 noexcept(比如某个子对象用了 std::vector<:string> 但 std::string 实现未保证移动不抛异常),整个类的 std::is_nothrow_move_constructible 就是 false——而这个链条很难肉眼排查。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











