C++中vector的push_back何时调用拷贝或移动构造函数?
vector::push_back 调用拷贝/移动构造函数的规律
问题描述
我在使用std::vector的push_back存入自定义Node对象时,无法预测它何时调用拷贝构造函数或移动构造函数。C++参考文档仅提到两种情况都可能发生,但未明确具体场景。我编写了测试代码后,观察到局部变量会触发拷贝构造函数,临时对象会触发移动构造函数,但不确定这是普遍规律还是巧合。
测试代码
#include <iostream> #include <vector> #include <unordered_set> #include <set> #include <unordered_map> #include <map> #include <queue> using namespace std; struct Node { int val; Node(int val) : val(val) { cout<<"created object" << val<<endl; } Node(const Node& m) : val(m.val) { cout<<"copy constructor is called on value " << m.val << endl; } ~Node() { cout<<"destroyed val" << val<<endl; } Node(Node&& other) noexcept : val(other.val) { cout<<"moved val " << other.val << endl; } }; void f(vector<Node>& a) { cout<<"______________________"<<endl; Node tmp(12); cout<<"12 established"<<endl; a.push_back(tmp); cout<<"a pushed back 12"<<endl; a.push_back(Node(14)); cout<<"a pushed back tmp obj 14"<<endl; tmp.val+=5; cout<<"increased tmp.val"<<endl; cout<<tmp.val<<endl; cout<<a[1].val<<endl; cout<<"two prints"<<endl; cout<<"_______________"<<endl; cout<<"end of f"<<endl; // return a; } int main() { vector<Node> a = {Node(125)}; //copied since initialized temp var. a.reserve(4000); cout<<"start of f"<<endl; f(a); cout<<"program ended"<<endl; //noteiced: Copy constructor called upon local variable (12) that the vector knows will not stay with it --- and belongs to local scope. //copy constructor not called upon temporary variable that soon belonged to vector (14). //same thing with std::queue, std::stack, and many others. //but it this a pattern or a coincidence? }
运行输出
copy constructor is called on value 125 destroyed val125 moved val 125 destroyed val125 start of f ______________________ created object12 12 established copy constructor is called on value 12 a pushed back 12 created object14 moved val 14 destroyed val14 a pushed back tmp obj 14 increased tmp.val 17 12 two prints _______________ end of f destroyed val17 program ended destroyed val125 destroyed val12 destroyed val14
规律解析
这不是巧合,完全符合C++标准规定的行为,核心逻辑基于左值/右值的判断,具体规则如下:
- 传递左值时调用拷贝构造函数:当传入的是有名字的对象(比如局部变量
tmp、全局变量),这类对象属于左值,代表后续可能被继续使用(比如你代码中修改了tmp.val)。C++不会自动窃取左值的资源,因此push_back会调用拷贝构造函数复制对象。如果想强制移动左值,可以用std::move()显式转换为右值,比如a.push_back(std::move(tmp))。 - 传递右值时调用移动构造函数:当传入的是临时对象(比如
Node(14))、或者std::move()转换后的左值,这类对象属于右值,代表其资源可以被安全“转移”。临时对象会在当前语句结束后立即销毁,因此push_back会调用移动构造函数,直接接管资源而非拷贝,效率更高。 - 容器扩容时的额外构造逻辑:如果
vector当前容量不足以容纳新元素,会触发内存重新分配,需要将旧内存中的元素转移到新内存。此时:- 如果移动构造函数标记为
noexcept(如你的代码所示),会调用移动构造函数完成转移; - 如果移动构造函数不是
noexcept,为了保证异常安全(移动过程中抛出异常时,旧元素不会丢失),会回退到拷贝构造函数。你的代码中提前调用了reserve(4000),避免了扩容,因此未触发这种情况。
- 如果移动构造函数标记为
- 初始化列表的特殊情况:你代码中
vector<Node> a = {Node(125)};的输出较为特殊,是因为初始化列表的机制:首先创建临时对象Node(125),初始化列表会先拷贝这个临时对象,随后vector再从初始化列表中移动元素到自身内存,最后临时对象被销毁。若想避免额外的拷贝,可以直接使用vector<Node> a{Node(125)};或emplace_back构造元素。
这类左值/右值的判断逻辑并非vector独有,std::queue、std::stack等标准容器的插入操作均遵循相同规则。
内容的提问来源于stack exchange,提问作者qwert789812
相关产品推荐
相关产品推荐

