push_back 变慢主因是扩容时的内存重分配、元素拷贝与析构;对非平凡类型开销更大,偶发卡顿;reserve(n) 可预分配空间避免扩容,比移动语义更治本。

push_back 为什么会突然变慢?
不是 push_back 本身慢,而是触发扩容时要重新分配内存、拷贝旧元素、再析构旧对象——这三步在 vector 容量不足时集中发生,造成「偶发性卡顿」。尤其当存的是非 trivial 类型(比如含指针或资源的类),拷贝和析构开销更大。
常见现象:循环调用 push_back 10 万次,前 99990 次很快,最后几次耗时飙升;用性能分析器看到大量 memcpy 或自定义拷贝构造函数被调用。
- vector 默认增长策略通常是「乘性扩容」(如 MSVC 和 libstdc++ 用 1.5 倍,libc++ 用 2 倍),但具体倍数不标准,不可依赖
- 每次扩容后,
capacity()跳变,size()线性增长,二者差值就是预留空间 - 若提前知道大概元素数量,
reserve(n)能彻底避免中间扩容
reserve 和 resize 的区别不能搞混
reserve(n) 只改变容量(capacity()),不改变大小(size()),也不调用任何构造函数;resize(n) 改变大小,会默认构造/析构元素,可能触发扩容,也可能不触发——取决于 n 是否超过当前 capacity()。
错误用法示例:v.resize(100000); v.push_back(x); —— 这会让 vector 先构造 10 万个默认对象,再 push,浪费时间和内存。
立即学习“C++免费学习笔记(深入)”;
- 只预分配空间用
reserve;需要初始化元素才用resize -
reserve后调用push_back不会触发扩容,直到size() == capacity() -
reserve传入 0 不释放内存(C++11 起标准规定),想缩容得用shrink_to_fit()(但它是非强制的)
移动语义能缓解但不能替代 reserve
如果 push 的是临时对象或右值(比如函数返回值、std::move(x)),且类型支持移动构造,push_back 会调用移动而非拷贝——这在扩容时能显著减少开销,但依然要重新分配内存、移动所有已有元素。
也就是说:移动语义优化的是「元素搬运成本」,而 reserve 解决的是「搬运次数」问题。二者互补,但后者更治本。
- 确保类有 noexcept 移动构造函数,否则 vector 扩容时可能退回到拷贝(为异常安全)
- 对 POD 类型(如
int、double),移动和拷贝没区别,此时reserve是唯一有效手段 - 用
emplace_back替代push_back可避免临时对象构造,但不影响扩容行为
什么情况下可以不 reserve?
小规模数据(size() )、一次性构建后只读、或性能不敏感路径下,省略 reserve 是合理选择。现代 STL 实现对小 vector 有 SSO(small string optimization)类似优化,但 vector 标准不保证 SSO,实际中多数实现仍走堆分配。
真正危险的是「动态估算 + 频繁 push」模式,比如解析网络包时逐字段 push_back,又无法预估长度——这时应改用其他结构(如 std::deque),或分批处理+预分配 buffer。
- 调试阶段可加断言:
assert(v.size() ,配合日志观察扩容频率 - 用
v.capacity() - v.size()监控剩余空间,比反复调用size()更便宜 - gcc/clang 下可开启
-D_GLIBCXX_DEBUG检测越界和低效操作,但仅限调试
vector 扩容不是黑盒,它由你控制的第一次 reserve 和后续每次 push_back 共同决定;最容易被忽略的,是把「避免拷贝」等同于「避免扩容」。









