文章目录

C++ emplace_back比push_back快在哪里

发布于 2026-05-02 11:14:38 · 浏览 6 次 · 评论 0 条

C++ emplace_back比push_back快在哪里

push_backemplace_back 的核心区别在于:是否在内存中“搬运”对象。理解这一点,就能明白为什么 emplace_back 更快。


理解核心区别:搬家 vs 现场组装

要把一件家具放进房间,有两种方法:

  1. push_back(搬家模式):先在走廊里把家具组装好,然后把它搬运进房间,最后把走廊里的那个拆掉或扔掉。
  2. emplace_back(现场模式):直接把木板和钉子拿进房间,在房间里的目标位置直接把家具组装好。

在 C++ 中,对象就是家具,内存就是房间。push_back 需要先构造一个临时对象,再移动或拷贝进容器;而 emplace_back 直接在容器的内存空间中构造对象,省去了中间的“搬运”过程。


观察代码运行过程

阅读下面的代码,我们定义一个简单的结构体 Widget,并在构造函数中打印日志,以便追踪对象的创建过程。

#include <iostream>
#include <vector>
#include <string>

struct Widget {
    std::string name;
    int value;

    // 构造函数
    Widget(std::string n, int v) : name(n), value(v) {
        std::cout << "构造函数被调用: " << name << std::endl;
    }

    // 拷贝构造函数
    Widget(const Widget& other) : name(other.name), value(other.value) {
        std::cout << "拷贝构造函数被调用: " << name << std::endl;
    }

    // 移动构造函数
    Widget(Widget&& other) noexcept : name(std::move(other.name)), value(other.value) {
        std::cout << "移动构造函数被调用: " << name << std::endl;
    }
};

int main() {
    std::vector<Widget> vec;

    std::cout << "--- 使用 push_back ---" << std::endl;
    vec.push_back(Widget("PushBackObj", 1)); // 传入临时对象

    std::cout << "\n--- 使用 emplace_back ---" << std::endl;
    vec.emplace_back("EmplaceBackObj", 2);   // 直接传入构造参数
    return 0;
}

分析这段代码的输出结果(忽略编译器优化的情况):

  • push_back
    1. 先调用一次构造函数(创建临时对象 Widget("PushBackObj", 1))。
    2. 再调用一次移动构造函数(将临时对象移动到 vector 内存中)。
    3. 最后析构临时对象。
  • emplace_back
    1. 仅调用一次构造函数(直接在 vector 内存中创建对象)。

显然,emplace_back 少走了一次“移动”和“析构”的流程。


查看执行流程对比

下面的流程图清晰地展示了两者在内存操作上的步骤差异。

graph LR subgraph P["push_back 执行流程"] P1[传入临时对象] --> P2[调用构造函数
生成临时对象] P2 --> P3[调用移动/拷贝构造函数
对象入容器] P3 --> P4[析构临时对象] end subgraph E["emplace_back 执行流程"] E1[传入构造参数] --> E2[在容器内存内
直接调用构造函数] end P3 -.-> E2

从图中可以看出,emplace_back 跳过了生成临时对象和处理临时对象的繁琐步骤。


掌握性能损耗公式

当对象很复杂(例如包含 std::stringstd::vector 等动态资源)时,移动构造虽然比拷贝构造快,但依然需要消耗时间。

对于 push_back,其总开销 $C_{push}$ 大约为:

$$ C_{push} = C_{construct} + C_{move} + C_{destruct} $$

而对于 emplace_back,其总开销 $C_{emplace}$ 仅为:

$$ C_{emplace} = C_{construct} $$

结论:$C_{move}$(移动开销)和 $C_{destruct}$(析构开销)就是 emplace_back 节省下来的成本。对于简单类型(如 int),两者差异微乎其微;但对于复杂对象,节省的成本非常可观。


遵循使用建议

在实际开发中,请遵循以下原则以确保代码既高效又安全:

  1. 优先使用 emplace_back
    在添加新元素时,默认使用 emplace_back,除非有特殊理由。

  2. 直接传入构造函数的参数。
    不要在 emplace_back 里创建对象,要像下面这样写:

    // 正确做法:直接传参数
    vec.emplace_back("ObjectName", 100);
    
    // 错误做法:传了一个临时对象(这样就退化成了 push_back)
    vec.emplace_back(Widget("ObjectName", 100));
  3. 注意参数类型匹配。
    如果构造函数是 explicit(显式)的,或者参数类型需要隐式转换,emplace_back 会直接进行转换并在原地构造,依然比先构造再移动要快。


对比特性差异

下表总结了两者在主要特性上的区别:

特性 push_back emplace_back
传入参数 已存在的对象 构造函数所需的参数
临时对象 需要生成 不需要生成
底层操作 拷贝或移动构造 原地构造(直接初始化)
适用场景 已有对象需要插入 需要新建对象并插入
代码可读性 较高(意图明显) 中等(需识别参数)

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文