vector::push_back扩容时申请新内存、拷贝/移动元素、释放旧内存,导致所有迭代器失效;新容量通常1.5或2倍增长,是否移动取决于类型是否满足noexcept可移动构造等条件。

vector::push_back 触发扩容时,内存是怎么重新分配的
底层不是“在原地加一块”,而是申请一块更大的新内存,把旧元素逐个拷贝(或移动)过去,再释放旧内存。这个过程涉及三步:计算新容量、operator new 分配、std::uninitialized_copy(或 std::move)搬运。
常见错误现象:std::bad_alloc 直接抛出,或者迭代器/指针全部失效——因为旧地址被 delete[] 了。哪怕只 push_back 一个元素,只要触发扩容,所有现存 iterator、pointer、reference 都变成悬垂状态。
- 新容量通常按 1.5 倍或 2 倍增长(GCC 用 1.5,MSVC 用 2),不是固定 +1
- 拷贝还是移动取决于元素类型是否满足
is_trivially_move_constructible;自定义类若没写移动构造,就退化为拷贝 - 如果元素析构函数抛异常,而搬运中途失败,C++ 标准要求已构造的部分必须正确析构,旧内存不能泄露——这会增加少量运行时开销
capacity() 和 size() 的差异直接影响扩容频率
size() 是当前存了多少个元素,capacity() 是目前分配了多少连续空间。只有 size() == capacity() 且还要插入时,才触发扩容。很多人误以为“只要 push_back 就慢”,其实真正代价来自扩容本身,而非插入动作。
使用场景:频繁拼接字符串、读取未知长度数据、构建临时结果集——这些地方若不预估大小,可能反复扩容 5–10 次。
立即学习“C++免费学习笔记(深入)”;
- 提前调用
reserve(n)可避免多次分配;但别 over-reserve,比如reserve(1000000)却只存 10 个,浪费内存且影响 cache 局部性 -
resize(n)会改变size(),也可能触发扩容(当n > capacity()),但它还会对新增位置调用默认构造,和reserve语义完全不同 - 空
vector的初始capacity()是 0(GCC)或未指定(标准允许),第一次push_back必定分配
自定义分配器下,扩容行为可能完全改变
标准 std::vector<t allocator></t> 允许传入自定义分配器,这意味着 allocate、deallocate、construct 等行为都可重载。一旦用了池式分配器或栈上分配器,扩容可能不走 malloc,甚至根本不“重新分配”——比如复用固定大小的 arena。
容易踩的坑:自定义分配器若没正确定义 max_size() 或 allocate 异常安全,会导致扩容时行为不可预测;更隐蔽的是,不同分配器之间无法交换内存(swap 可能失效)。
- 调试时注意:用
get_allocator()检查实际使用的分配器类型,别假设是std::allocator - 跨线程使用同一
vector时,若分配器非线程安全(如某些内存池),扩容可能引发竞态 - Clang libc++ 在 debug 模式下会注入额外检查,可能导致自定义分配器的
allocate被调用两次(一次试探,一次真实),需兼容
移动语义没生效?检查你的元素类型是否满足条件
扩容搬运阶段是否调用移动构造而非拷贝构造,不只看有没有写 T(T&&),还要看整个类型是否满足 std::is_nothrow_move_constructible_v<t></t> 且 std::is_trivially_destructible_v<t></t> 等隐含要求。否则 vector 会保守选择拷贝。
典型表现:自定义类写了移动构造,但扩容时仍调用拷贝构造函数,gdb 断点里看到 __uninitialized_move_if_noexcept_a 内部跳转到了拷贝分支。
- 用
static_assert(std::is_nothrow_move_constructible_v<mytype>)</mytype>显式验证 - 若移动构造函数声明为
noexcept,编译器更容易启用移动优化 - 含有
std::mutex成员的类天然不满足移动条件(mutex不可移动),此时扩容必然拷贝,且拷贝还可能失败










