当前位置: 首页 > news >正文

C++零基础到工程实战(4.3.6):vector中push_back和emplace_back性能分析

目录

一、前言

二、本节代码示例

三、push_back 和 emplace_back 都是做什么的

3.1 push_back:把一个已有元素放到vector尾部

3.2 emplace_back:直接在vector尾部构造元素

四、push_back 与 emplace_back 的插入过程分析

4.1 vs.push_back(string("test")); 的执行逻辑

(1)先构造一个临时 string 对象

(2)vector 尾部准备一个位置

(3)把这个临时 string 放到容器里

4.2 vs.push_back("test"); 的执行逻辑

4.3 vs.emplace_back("test"); 的执行逻辑

(1)vector 先在尾部准备好一块空间

(2)把 "test" 作为参数,直接传给 string 的构造函数

(3)在这块尾部空间中直接构造出这个 string 对象

4.4 vs.emplace_back(string("test")); 的执行逻辑

4.5 vs.emplace_back(); 是什么意思

五、性能分析:emplace_back为什么通常更高效

5.1 中间临时对象更少

5.2 对复杂对象优势更明显

5.3 但不是所有场景都差很多

六、结合性能因素:扩容

七、本节重点总结

7.1 push_back 的特点

7.2 emplace_back 的特点

7.3 最容易误解的地方

八、小结


一、前言

在前面的内容中,我们已经学习了vector的常见操作,也知道vector作为动态数组容器,在工程开发中使用非常频繁。

当我们往vector尾部插入元素时,最常用的有两个函数:

  • push_back()
  • emplace_back()

很多初学者第一次看到这两个函数时,会觉得它们功能几乎一样:

  • 都能在尾部插入元素
  • 都能让vector增加新内容
  • 看起来经常可以互相替代

于是就会产生一个问题:

既然都能插入元素,那为什么 C++ 还要设计两个接口?它们到底有什么区别?

这背后其实涉及到一个很重要的性能问题:

  • push_back往往是先有对象,再放进容器
  • emplace_back往往是直接在容器内部构造对象

如果元素类型比较简单,这种差别可能不明显;
但如果元素类型比较复杂,比如string、自定义类对象、大对象等,这种差别就可能带来一定的性能影响

本节我们就结合string和代码示例,详细分析:

  • push_back的插入过程
  • emplace_back的插入过程
  • 哪些场景下emplace_back更高效
  • 为什么有时两者差别不大
  • 为什么emplace_back(string("test"))不一定比push_back(string("test"))更占优势


二、本节代码示例

#include <iostream> #include <vector> #include <string> using namespace std; int main() { // vector插入元素的效率 { vector<string> vs; // push_back:先构造对象,再放入容器 vs.push_back(string("test")); // emplace_back:这里虽然用了emplace_back, // 但string("test")这个临时对象依然已经先构造出来了 vs.emplace_back(string("test")); // push_back:隐式把"test"转换成string,再放入容器 vs.push_back("test"); // emplace_back:直接把"test"作为参数, // 在vector尾部原地构造string对象 vs.emplace_back("test"); // 默认构造一个空字符串 vs.emplace_back(); for (auto& s : vs) { cout << "[" << s << "]" << endl; } } return 0; }

三、push_back 和 emplace_back 都是做什么的

3.1 push_back:把一个已有元素放到vector尾部

push_back的字面意思就是:

把一个元素推到末尾。

例如:

vector<string> vs; vs.push_back("test");

这句代码的效果是:

vs的尾部增加一个字符串元素"test"

但它的思路通常是:

先准备好一个对象,再把这个对象放入容器。

也就是说,push_back更像是在说:

“我这里已经有一个东西了,请你把它塞进 vector 末尾。”


3.2 emplace_back:直接在vector尾部构造元素

emplace_back的字面意思可以理解为:

在末尾原地构造。

例如:·

vs.emplace_back("test");

它的思路通常是:

不用先在外面准备一个完整对象,而是直接把构造参数交给容器,让容器在自己的内部空间里把对象构造出来。

所以emplace_back更像是在说:

“你不要等我先把对象造好,你直接在容器内部帮我把它创建出来。”

这也就是它被认为“更高效”的核心原因。


四、push_back 与 emplace_back 的插入过程分析

4.1vs.push_back(string("test"));的执行逻辑

(1)先构造一个临时string对象

代码:

string("test")

这里先根据"test"创建了一个临时的string对象。

也就是说,在调用push_back之前,对象其实已经先存在了。

(2)vector尾部准备一个位置

vector会检查自己当前是否还有容量。

  • 如果容量够,就直接使用尾部空位
  • 如果容量不够,就会先扩容,再准备新位置

(3)把这个临时string放到容器里

早期 C++ 中,这里往往是拷贝进去。
现代 C++ 中,如果传入的是右值临时对象,通常会优先使用移动语义,也就是把临时对象“移动”进容器。

先在外部构造一个临时对象,再把这个对象移动到容器内部。


4.2vs.push_back("test");的执行逻辑

代码:

vs.push_back("test");

这里看起来没有写string("test"),但并不意味着没有构造string对象。

因为vs的类型是:

vector<string>

所以push_back最终要插入的是string类型元素。

"test"本身是字符串字面量,本质上更接近:

const char*

因此,push_back("test")背后的逻辑通常可以理解为:

(1)先根据"test"隐式构造一个临时string

(2)再把这个临时string放入vector尾部

(3)现代 C++ 中通常是移动进去

所以:

vs.push_back("test");

虽然写法更短,但它本质上还是“先构造一个对象,再放进去”。


4.3vs.emplace_back("test");的执行逻辑

代码:

vs.emplace_back("test");

这句才是emplace_back最典型、最有代表性的用法

它的执行过程通常可以理解为:

(1)vector先在尾部准备好一块空间

(2)把"test"作为参数,直接传给string的构造函数

(3)在这块尾部空间中直接构造出这个string对象

也就是说:

没有先在外部单独生成一个临时string,而是直接在容器内部构造。

这就是所谓的:

原地构造、就地构造。

所以从机制上说:

vs.emplace_back("test");

通常比:

vs.push_back("test");

更贴近“少一次中间对象转换”的思路。


4.4vs.emplace_back(string("test"));的执行逻辑

代码:

vs.emplace_back(string("test"));

这一句非常值得专门讲,因为很多人会误以为:

只要写了emplace_back,就一定比push_back更高效。

其实不一定。

因为这里你已经先写了:

string("test")

这说明:

临时string对象已经在外部先构造出来了。

也就是说,这一句已经不是最纯粹的“直接在容器内部构造对象”了。

它更像是:

(1)先在外部生成一个临时string

(2)再把这个临时string作为参数传给emplace_back

(3)容器再利用这个参数在内部构造元素

所以从效果上看:

vs.emplace_back(string("test"));

和:

vs.push_back(string("test"));

差别就没有你想象中那么大。

真正能体现emplace_back优势的,通常不是这种写法,而是:

vs.emplace_back("test");

因为这里直接把构造参数交给了容器。


4.5vs.emplace_back();是什么意思

代码:

vs.emplace_back();

这句表示:

vector末尾直接构造一个默认的string对象。

对于string来说,默认构造出来的是一个空字符串

所以这句代码的含义就是:

在容器尾部新增一个空字符串元素。

这也是emplace_back很灵活的地方:

它不是只能传某个完整对象,而是可以直接传构造参数,甚至可以什么都不传,让它调用默认构造函数。


五、性能分析:emplace_back为什么通常更高效

5.1 中间临时对象更少

如果使用:

vs.emplace_back("test");

容器可以直接在自己的尾部空间构造string

相比之下:

vs.push_back("test");

通常要先把"test"转成临时string,再放入容器。

所以在很多场景下,emplace_back可以减少一次临时对象构造与转移过程。


5.2 对复杂对象优势更明显

如果元素类型只是int这种简单类型,那么:

vector<int> v; v.push_back(10); v.emplace_back(10);

两者差别通常很小,几乎没什么可感知的性能差异。

但如果元素类型是:

  • string
  • 大型对象
  • 包含资源管理的对象
  • 自定义复杂类

那么少一次临时对象构造、少一次移动或拷贝,就更有意义。

所以emplace_back的优势,在复杂对象场景下更容易体现出来。


5.3 但不是所有场景都差很多

这里也要讲得客观一些。

虽然大家常说:

emplace_backpush_back更高效

但在实际代码里,不一定每次都能感知到明显差距。因为现代 C++ 已经有:

  • 移动语义
  • 编译器优化
  • 小对象优化(例如部分string实现)

所以很多时候:

push_back(string("test"))

emplace_back(string("test"))

性能差距可能并不大。

真正差异更明显的,是:

push_back("test")

emplace_back("test")

因为后者更接近“直接原地构造”。


六、结合性能因素:扩容

前面我们分析的是“单次插入时对象构造方式的差异”,但在实际开发中,影响vector插入性能的,往往还有一个更大的因素:

扩容。

例如:

vector<string> vs; for (int i = 0; i < 10000; i++) { vs.emplace_back("test"); }

如果vs一开始没有预留足够容量,那么中间会多次扩容。每次扩容都可能涉及:

  • 申请新内存
  • 搬移已有元素
  • 释放旧内存

这个开销,往往比你单次push_backemplace_back的微小差距更大。

所以从工程角度说,真正想提升插入性能时,不能只盯着push_backemplace_back的区别,还要记得:

vs.reserve(预计数量);

提前预留空间

例如:

vector<string> vs; vs.reserve(10000); for (int i = 0; i < 10000; i++) { vs.emplace_back("test"); }

这样通常会更高效。


七、本节重点总结

7.1 push_back 的特点

(1)插入一个已有对象

(2)通常是先构造对象,再放入容器

(3)现代 C++ 中对右值通常优先移动而不是拷贝


7.2 emplace_back 的特点

(1)在容器尾部直接构造对象

(2)通过传构造参数来创建元素

(3)对复杂对象通常更高效

(4)最能体现优势的写法是直接传构造参数,例如emplace_back("test")


7.3 最容易误解的地方

(1)不是所有emplace_back都一定明显更快

(2)emplace_back(string("test"))依然已经先创建了临时对象

(3)真正影响整体性能的还有vector扩容

(4)工程里经常配合reserve()一起优化插入效率


八、小结

本节我们学习了vector中两个非常常见的尾部插入函数:

push_back() emplace_back()

它们看起来都能“往尾部加元素”,但背后的设计思路并不一样:

  • push_back更偏向把现成对象放进去
  • emplace_back更偏向直接在容器内部构造对象

因此,从机制上说,emplace_back往往更贴近高效设计,尤其是在元素类型较复杂时更有意义。

不过真正写代码时也要记住:

不要机械地认为emplace_back一定绝对更快,而要看你是不是直接传了构造参数,以及容器是否发生扩容。

http://www.jsqmd.com/news/673181/

相关文章:

  • Python提高:条件断点的详解-由Deepseek产生
  • 【收藏备用】2026年AI行业最大机会在应用层!大模型岗位暴增,程序员入门必看
  • zmq源码分析之请求模式数据发送
  • 光学频率梳市场:全球市场年复合增长率(CAGR)为8.3%(2026-2032)
  • [特殊字符] Meixiong Niannian画图引擎效果实测:1024×1024输出在印刷级DPI下的表现
  • 2026年同人谷快递哪家靠谱?物流服务选择解析 - 品牌排行榜
  • 华硕笔记本终极控制指南:3分钟用G-Helper告别Armoury Crate臃肿烦恼!
  • 2026春季下学期第八周
  • 2026知名瓷砖展架厂家有哪些 - 品牌企业推荐师(官方)
  • Ryzen SDT 1.37:深度解析AMD处理器底层调试与性能调优工具
  • VS2019下OpenCV C++环境配置保姆级教程(附4.4.0版本动态库文件清单)
  • 嘉兴企业微信服务商排行榜及服务特色解析 - 品牌排行榜
  • 广东好用的灌胶机供应商有哪些? - 品牌企业推荐师(官方)
  • GitHub导航菜单全解析:涵盖平台、解决方案、资源等多方面功能
  • python codefresh
  • Java项目Loom迁移实战:7步完成响应式编程转型,附完整配置代码与性能对比数据
  • 从音乐播放器到智能音箱:聊聊DFT/FFT在我们身边那些‘看不见’的应用
  • 2026生物质燃料口碑好的企业有哪些 - 品牌企业推荐师(官方)
  • 树莓派PICO的‘Hello World’:用MicroPython和Thonny让板载LED闪起来(含代码详解)
  • SeanLib系列函数库-MyTimer
  • 如何在Windows上直接安装安卓应用:APK Installer完整指南
  • OpCore-Simplify:10分钟自动化完成黑苹果配置的智能解决方案
  • 5个核心技术解析:在Windows 10上专业部署Android子系统的完整指南
  • go: Flyweight Pattern
  • Android开发避坑指南:OkHttp3.14+导入时别忘了配置network_security_config.xml
  • 基于KITTI数据集:从LIO-SAM部署到EVO精度评估全流程解析
  • 保姆级教程:用VMware Workstation Pro搭建CFS三层靶场(附宝塔面板配置与网络排错)
  • 【AI面试八股文 Vol.1.1 | 专题6:Checkpoint 机制】Checkpoint机制:状态持久化与断点恢复
  • #官方认证|2026年长三角1大正规通道闸公司排名,上海苏州嘉兴等地骏通智能综合实力遥遥领先 - 十大品牌榜
  • 理性择校:合肥雅思机构排名视角下,哪个更值得优选 - 资讯焦点