You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 10:33:13