导读:本期聚焦于小伙伴创作的《C++中emplace_back和push_back到底差在哪?构造与拷贝性能实测》,敬请观看详情。把一个临时对象塞进vector时,push_back往往要先构造再拷贝或移动,而emplace_back能直接在容器内存里构造。这种差异在高频写入或大型对象场景下会被放大。本文用带计时器的基准代码对比两者在基础类型、含拷贝构造函数对象和只移动类型的耗时,说明何时该换用emplace_back。同时指出并非所有情况都有明显收益,错误使用反而降低可读性。理解底层参数转发机制,才能写出既快又稳的C++容器操作代码。

在C++标准库的vector容器中,向尾部添加元素最常用的两个接口是push_back和emplace_back。它们表面上都能完成插入,但在对象构造路径上存在本质区别。push_back接收的是已经构造好的对象,而emplace_back接收构造参数,并借助完美转发在容器内部直接构造。这种机制差异会在特定场景中带来显著的性能变化。

C++中emplace_back和push_back到底差在哪?构造与拷贝性能实测

一、底层机制差异

push_back有两种重载形式:一种接受const引用,另一种接受右值引用。当传入左值时会调用拷贝构造函数,传入临时对象时会优先匹配移动构造。无论哪种情况,对象都必须在调用前完成一次构造,随后再被拷贝或移动到vector的存储空间中。这意味着至少发生一次额外的构造或移动操作。

emplace_back则不同,它使用可变参数模板和完美转发,将参数原样传递给元素类型的构造函数。vector会先分配或复用一块原始内存,然后在这块内存上直接调用构造函数,省去了外部的临时对象构造以及后续的拷贝或移动。对于没有移动构造或者移动成本较高的类型,这种差异尤其明显。

#include <vector>
#include <string>

struct Data {
    int id;
    std::string name;
    Data(int i, const std::string& n) : id(i), name(n) {
        // 构造函数
    }
};

int main() {
    std::vector<Data> v;
    v.reserve(10);
    // push_back:先构造临时Data,再移动或拷贝进vector
    v.push_back(Data(1, "test"));
    // emplace_back:直接在vector内存中构造Data
    v.emplace_back(2, "demo");
    return 0;
}

二、性能测试设计

为了直观对比,我们设计三组测试:基础类型int、包含显式拷贝构造和移动构造的自定义类型、以及只支持移动的大对象。每组执行十万次插入,并使用chrono计时。为避免vector扩容干扰,提前调用reserve预留空间。

测试环境为普通桌面编译器开启O2优化。自定义类型在构造、拷贝、移动时分别打印或计数,以便确认调用路径。通过统计耗时和构造次数,可以清楚看到emplace_back在自定义类型上的优势,而在int这类平凡类型上两者几乎无差。

#include <vector>
#include <chrono>
#include <iostream>

struct Heavy {
    int buf[64];
    Heavy() {}
    Heavy(const Heavy&) { /* 拷贝开销大 */ }
    Heavy(Heavy&&) { /* 移动开销小 */ }
};

int main() {
    const int N = 100000;
    {
        std::vector<Heavy> v;
        v.reserve(N);
        auto t1 = std::chrono::steady_clock::now();
        for (int i = 0; i < N; ++i) {
            v.push_back(Heavy());
        }
        auto t2 = std::chrono::steady_clock::now();
        std::cout << "push_back: "
                  << std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count()
                  << " msn";
    }
    {
        std::vector<Heavy> v;
        v.reserve(N);
        auto t1 = std::chrono::steady_clock::now();
        for (int i = 0; i < N; ++i) {
            v.emplace_back();
        }
        auto t2 = std::chrono::steady_clock::now();
        std::cout << "emplace_back: "
                  << std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count()
                  << " msn";
    }
    return 0;
}

三、测试结果与分析

在自定义Heavy类型的测试中,push_back由于先构造临时对象再移动,总耗时通常比emplace_back高出一定比例。emplace_back直接在容器内构造,减少了一次移动构造调用。对于int等平凡类型,两者生成的汇编几乎一致,性能差异可以忽略。

需要注意,如果元素类型没有 noexcept 移动构造,vector扩容时仍可能使用拷贝构造,此时emplace_back也无法避免拷贝。另外,emplace_back的参数转发有时会导致意料之外的构造函数匹配,例如用单个整数去构造本应接收初始化列表的对象,因此代码可读性要权衡。

类型push_back耗时emplace_back耗时差异原因
int约3ms约3ms平凡构造无额外开销
Heavy(有移动构造)约12ms约8ms少一次移动构造
无移动构造对象约25ms约25ms均退化为拷贝

四、使用建议

当插入的是临时对象且类型移动成本低时,push_back和emplace_back差距不大;但当类型构造昂贵、或需要避免额外移动、或参数为多段构造值时,优先使用emplace_back。对于基础类型,按代码清晰度选择即可。

在泛型代码中,若不确定元素类型,emplace_back通常更安全且高效,但仍需测试验证。不要盲目替换所有push_back,应结合性能剖析与构造函数设计,才能发挥C++零开销抽象的优势。

emplace_backpush_backC++_性能测试修改时间:2026-08-02 05:54:30

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。