多次调用emplace_back额外触发拷贝构造函数的问题分析
嘿,这个问题的核心其实藏在std::vector的动态内存管理机制里,咱们结合你的代码和输出一点点拆解~
首先,得先搞懂std::vector的扩容逻辑:vector本质是个动态数组,它会预先分配一块连续的内存(叫做容量capacity)来存储元素。当你插入元素时,如果当前元素的数量(size)已经等于容量,vector就不得不进行扩容操作:
- 分配一块更大的新内存空间(通常是原容量的2倍,不同实现可能有差异);
- 把旧内存里的所有元素转移到新内存;
- 销毁旧内存里的元素并释放旧内存;
- 最后在新内存的末尾插入新元素。
为什么触发的是拷贝构造,不是移动构造?
你已经给类S写了移动构造函数S(S &&),但C++标准里有个关键规则:只有当移动构造函数被标记为noexcept时,vector扩容才会优先使用移动构造。
这是出于异常安全的考虑:如果移动构造过程中抛出异常,vector没法保证旧内存里的元素还处于有效状态,数据可能丢失;但拷贝构造如果抛出异常,旧元素还好好待在旧内存里,不会出问题。所以你的移动构造函数没加noexcept,编译器就默认它可能抛出异常,于是 fallback 到了更安全的拷贝构造。
结合你的代码输出一步步看:
咱们对应你的代码和输出细节:
第一次调用
s.emplace_back():- 初始时
vector是空的,分配了初始容量(比如1); - 直接在
vector的内存里构造第一个S对象,输出S()。此时vector的size=1,capacity=1。
- 初始时
第二次调用
s.emplace_back(6):- 此时
size等于capacity,触发扩容; - 因为移动构造没有
noexcept,vector把旧内存里的S对象拷贝到新内存,输出S(const S&); - 在新内存的末尾构造第二个
S对象(用int参数),输出S(int); - 销毁旧内存里的那个
S对象,输出~S(); - 程序结束时,
vector里的两个S对象被销毁,输出最后两个~S()。
- 此时
如何验证和解决?
你可以做两个小测试来验证这个逻辑:
给移动构造加
noexcept:
修改移动构造函数为:S(S &&) noexcept { puts("S&&"); }再运行代码,输出就会变成
S()→S&&→S(int)→~S()→~S()→~S(),此时触发的是移动构造而非拷贝构造。提前预留足够容量:
在main里提前给vector预留足够的容量,避免扩容:int main() { std::vector<S> s; s.reserve(2); // 提前预留能存2个元素的空间 s.emplace_back(); s.emplace_back(6); }输出会变成
S()→S(int)→~S()→~S(),完全不会触发拷贝/移动构造,因为不需要扩容了。
内容的提问来源于stack exchange,提问作者Sitesh

