push_back和emplace_back

devtools/2024/10/24 20:10:19/

title: push_back和emplace_back
date: 2024-10-20 19:29:25
tags:
categories:

  • C++

modern C++中提到使用考虑使用置入代替插入,个人理解就是使用考虑使用emplace_back代替push_back操作,那么这两者之间究竟有什么区别,想探究一下

首先这两个函数的定义是不一样的

函数定义

push_back是有两个函数的(重载),一个接受左值一个接受右值,并且接受右值后进行了move

    _CONSTEXPR20 void push_back(const _Ty& _Val) { // insert element at end, provide strong guarantee_Emplace_one_at_back(_Val);}_CONSTEXPR20 void push_back(_Ty&& _Val) {// insert by moving into element at end, provide strong guarantee_Emplace_one_at_back(_STD move(_Val));}

emplace_back是只有一个函数,是一个模板函数,参数是一个通用引用并且是变长参数,然后进行了完美转发forward

    template <class... _Valty>_CONSTEXPR20 decltype(auto) emplace_back(_Valty&&... _Val) {// insert by perfectly forwarding into element at end, provide strong guarantee// 完美转发直接将参数传入内部_Ty& _Result = _Emplace_one_at_back(_STD forward<_Valty>(_Val)...);
#if _HAS_CXX17return _Result;
#else // ^^^ _HAS_CXX17 / !_HAS_CXX17 vvv(void) _Result;
#endif // _HAS_CXX17}

此外,push_backemplace_back都使用了_Emplace_one_at_back进行插入

差异

性能上

先说结论:

理论上来说,emplace_backpush_back效率更高。emplace_back能够在vector内部构建元素,从而减少拷贝或者移动操作

这句话怎么理解呢,举个例子

std::vector<std::string> vs;        //std::string的容器
vs.push_back("xyzzy");              //添加字符串字面量

通过上面的源码我们可以看到push_back接受的参数是一个T的元素,但是这里传入的是字面量,所以在这里会通过字面量创建出一个临时变量(隐式转换),等价于下面的代码

vs.push_back(std::string("xyzzy")); //创建临时std::string,把它传给push_back

综上vs的push_back总共有三个操作

  1. 一个std::string的临时对象从字面量“xyzzy”被创建。这个对象没有名字,我们可以称为temptemp的构造是第一次std::string构造。因为是临时变量,所以temp是右值。
  2. temp被传递给push_back的右值重载函数,绑定到右值引用形参_Val。在std::vector的内存中一个_Val的副本被创建。这次构造——也是第二次构造——在std::vector内部真正创建一个对象。
  3. push_back返回之后,temp立刻被销毁,调用了一次std::string的析构函数。

当我们使用emplace_back时,

vs.emplace_back("xyzzy");           //直接用“xyzzy”在vs内构造std::string

emplace_back使用完美转发将"xyzzy"传入了vector内部(就是前面 _Ty& _Result = _Emplace_one_at_back(_STD forward<_Valty>(_Val)...);),直接在内部的数组的末尾构建元素插入,减少了临时变量的产生,提高了效率。

接受参数上

emplace_back使用完美转发,因此只要你没有遇到完美转发的限制(完美转发也会失败,在这里不多讲解),就可以传递任何实参以及组合到emplace_back

比如

vs.emplace_back(50, 'x');           //插入由50个“x”组成的一个std::string
vs.push_back(50, 'x');				// error
vs.push_back(std::string(50, 'x'))  // fine

再比如下面这种情况,临时变量都不给你转化,只能用emplace_back传入

class A
{
public:explicit A(int a) :m_a(a) {}
private:int m_a;
};std::vector<A> aVec;
aVec.push_back(1);		// error  
aVec.emplace_back(1);	// fine

所以写起来emplace_back肯定是更加舒服的,少写好多字母(理论上减少出错)。

代码实验

实验一(emplace_back高效性)

使用push_back

class BaseClass
{
public:BaseClass(const std::string name) : name_(name){std::cout << name_ << " constructor called" << std::endl;}BaseClass(const BaseClass& b) :name_(b.name_){std::cout << name_ << " copy constructor called" << std::endl;}BaseClass(BaseClass&& b){// 此处只是演示,并未进行真正移动name_ = b.name_;b.name_ = b.name_ + " have move";std::cout << name_ << " move constructor called" << std::endl;}virtual ~BaseClass(){std::cout << name_ << " destructor called" << std::endl;}
private:std::string name_;
};int main(int argc, char** argv)
{std::vector<BaseClass> bcVec;std::cout << "--------------------------------push_back :" << std::endl;bcVec.push_back(BaseClass("push_back_obj"));// push_back://    (1) 调用 有参构造函数 BaseClass (const std::string name) 创建临时对象;//    (2)调用 移动构造函数 BaseClass(BaseClass&& b) 到vector中;//    (3) 调用     析构函数               销毁临时对象;std::cout << "--------------------------------destruct:" << std::endl;//   (4) vector进行析构,调用析构函数 
}

运行结果符合预期

使用emplace_back

int main(int argc, char** argv)
{std::vector<BaseClass> bcVec;std::cout << "--------------------------------emplace_back :" << std::endl;bcVec.emplace_back("emplace_back_obj");// (1) 在vector中直接调用构造函数创建元素std::cout << "--------------------------------destruct:" << std::endl;// (2) vector进行析构,调用析构函数 
}

运行结果

可以看得出来emplace_back少临时变量的构造、移动、销毁操作,效率要高一些

实验二(两者都传入右值)

如果传入右值,push_back 和 emplace_back效率相同,都会有临时变量产生的构造、移动、销毁操作。

push_back传入右值:

std::cout << "--------------------------------push_back rvalue:" << std::endl;
bcVec.push_back(BaseClass("push_back_rvalue"));// push_back://    (1) 调用 有参构造函数 BaseClass (const std::string name) 创建临时对象;//    (2)调用 移动构造函数 BaseClass(BaseClass&& b) 到vector中;//    (3) 调用     析构函数               销毁临时对象;
std::cout << "--------------------------------destruct:" << std::endl;//   (4) vector进行析构,调用析构函数 

上面已经展示过了,这里就不多解释了。

emplace_back传入右值:

int main(int argc, char** argv)
{std::vector<BaseClass> bcVec;std::cout << "--------------------------------emplace_back rvalue:" << std::endl;bcVec.emplace_back(BaseClass("emplace_back_rvalue"));//    (1) 调用 有参构造函数 BaseClass (const std::string name) 创建临时对象;//    (2)调用 移动构造函数 BaseClass(BaseClass&& b) 到vector中;//    (3) 调用     析构函数               销毁临时对象;std::cout << "--------------------------------destruct:" << std::endl;//   (4) vector进行析构,调用析构函数 }

运行结果,可以看出效率没有提高

实验三(两者都传入左值)

如果传入右值,push_back 和 emplace_back效率相同,两者都会调用拷贝构造函数

push_back传入左值:

int main(int argc, char** argv)
{std::vector<BaseClass> bcVec;std::cout << "--------------------------------push_back lvalue:" << std::endl;
//  (1) 调用 有参构造函数 BaseClass (const std::string name) 创建obj对象;BaseClass obj("obj");
//  (2) 调用 拷贝构造函数;bcVec.push_back(obj);std::cout << "--------------------------------destruct:" << std::endl;
//  (3) obj被析构,调用BaseClass的析构函数
//  (4) vector被析构,其中的元素调用BaseClass的析构函数}

emplace_back传入左值:

int main(int argc, char** argv)
{std::vector<BaseClass> bcVec;std::cout << "--------------------------------emplace_back lvalue:" << std::endl;
//  (1) 调用 有参构造函数 BaseClass (const std::string name) 创建obj对象;BaseClass obj("obj");
//  (2) 调用 拷贝构造函数;bcVec.emplace_back(obj);std::cout << "--------------------------------destruct:" << std::endl;
//  (3) obj被析构,调用BaseClass的析构函数
//  (4) vector被析构,其中的元素调用BaseClass的析构函数
}

参考:

  • C++姿势点: push_back和emplace_back

http://www.ppmy.cn/devtools/128506.html

相关文章

基于单片机的搬运机器人控制系统

摘 要: 搬运机器人(transfer robot)是可以进行自动化搬运作业的工业机器人 。 搬运机器人可以通过C 语言编程实现单片机上控制,通过实验和调试,实行机器人的抓放料,也可以实现自己的自行运行。 关键词: 单片机;搬运机器人;调试 0 引 言 单片机又称单片微控制器 , 它…

torch.utils.checkpoint.checkpoint介绍

torch.utils.checkpoint.checkpoint 是 PyTorch 提供的一种内存优化工具&#xff0c;用于在计算图的反向传播过程中节省显存。它通过重新计算某些前向传播的部分&#xff0c;减少了保存中间激活值所需的显存&#xff0c;特别适用于深度模型&#xff0c;如 Transformer 等层数较…

(A-D)AtCoder Beginner Contest 376

目录 比赛链接&#xff1a; A - Candy Button 题目链接&#xff1a; 题目描述&#xff1a; 数据范围&#xff1a; 输入样例&#xff1a; 输出样例&#xff1a; 样例解释&#xff1a; 分析&#xff1a; 代码&#xff1a; B - Hands on Ring (Easy) 题目链接&#xff1…

代码随想录算法训练营第三十七天|509. 斐波那契数,70. 爬楼梯,746. 使用最小花费爬楼梯

509. 斐波那契数&#xff0c;70. 爬楼梯&#xff0c;746. 使用最小花费爬楼梯 509. 斐波那契数70. 爬楼梯746. 使用最小花费爬楼梯 509. 斐波那契数 斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c;后面…

智慧楼宇平台,构筑未来智慧城市的基石

随着城市化进程的加速&#xff0c;城市面临着前所未有的挑战。人口密度的增加、资源的紧张、环境的恶化以及对高效能源管理的需求&#xff0c;都在推动着我们寻找更加智能、可持续的城市解决方案。智慧楼宇作为智慧城市建设的重要组成部分&#xff0c;正逐渐成为推动城市可持续…

「AIGC」AI设计工具 v0.dev

https://v0.dev/ 1.1 简介 $20 学习前端代码本身似乎并不复杂,但是有平台能够直接生成代码、预览效果,似乎更有性价比。可能对于有前端和开发经验的同学而言,直接实现某个页面效果并不算是太复杂的事情,但是对于没有代码经验的同学而言,直接使用 AI 跑出代码甚至直接落地…

Chainlit集成LlamaIndex和Chromadb实现RAG增强生成对话AI应用

前言 本文主要讲解如何使用LlamaIndex和Chromadb向量数据库实现RAG应用&#xff0c;并使用Chainlit快速搭建一个前端对话网页&#xff0c;实现RAG聊天问答增强的应用。文章中还讲解了LlamaIndex 的CallbackManager回调&#xff0c;实现案例是使用TokenCountingHandler&#xf…

云渲染分布式渲染什么意思?一文详解

渲染和分布式渲染是现代计算机图形学中的重要技术&#xff0c;它们通过将渲染任务分散到多个服务器或计算节点上&#xff0c;显著提高了渲染效率和处理大规模数据的能力。这项技术在动画制作、游戏开发和电影特效等领域发挥着关键作用&#xff0c;为创作者提供了更快速、更灵活…