c++11中emplace_back vs push_back

引言

在C11中,有两种方法可以把元素放入容器中:emplace_back和push_back。

push_back是C11之前就有的,而emplace_back是C11中新加的。

既然它们的作用都是一样的,那么为什么C11中又加入了一个emplace_back?

既生瑜,何生亮?

在实际的项目编码中,到底用哪个呢?

优先选用emplace_back

考虑下面这段非常常见的代码:

std::vector<string> vs;

vs.push_back("xyz");

其中,vs容器持有的是string类型的对象,而在push_back的时候传入的是字符串字面量(不是string),换句话说,传递给push_back的实参并非容器持有物的类型。

同时我们也知道,这段代码没有问题,能够通过编译并正常运行。但是它背后都执行了哪些操作呢?

我们来看一下vector的push_back,它针对左值和右值给出了不同的重载版本:

template<class T, class Allocator = alloctor<T>> // c++11标准
class vector
{
	public:
		// ...
		void push_back(const T& x); // 左值
		void push_back(T&& x); // 右值
		// ...
};

现在回头看一下上面的那段代码,它会从字符串字面量出发创建string类型的临时对象,并将该临时对象传递给push_back,如下:

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

之所以要说明一下push_back后面的一些过程,是为了说明上面这句代码,虽然在功能上没有任何问题,但它可能存在性能上问题,因为它共执行了2次构造和1次析构:

  • 从字符串字面量“xyz”,创建string临时对象。临时对象没有名字,可以称为temp。这是第一次构建,因为是临时对象,所以temp是右值。
  • temp传递给push_back的右值重载版本,它被绑定到右值引用形参x。然后会在内存中为vector构造一个x的副本,这是第2次构造,在vector内创建了一个新的对象。
  • 在push_back返回的时候,temp析构。

那么,有没有方法能将字符串字面量直接传递给vector内构造的string对象,从而避免temp对象的构造和析构呢?

有!emplace_back就可以!

emplace_back使用完美转发,它使用传入的任何实参,在vector内构造一个string,不会涉及任何临时对象。

emplace_back之所以比push_back更牛逼,是因为它提供了更加灵活的接口:

  • push_back接受的是待插入对象
  • emplace_back接受的是待插入对象的构造函数实参

所以emplace_back能够避免临时对象的创建和析构。

即使在push_back燕不要求创建临时对象的情况下,也可以使用emplace_back,这时,它们两个做的是同一件事情。

综上所述,emplace_back能做到push_back所能够做到的一切事情,而且前者可能比后者更高效,那么,何不总是使用emplace_back呢?

push_back的用武之地

然而,emplace_back比push_back的效率有时更高,是理论上的。

在实际实践中,还是要根据看传递的实参类型、容器种类、插入位置、容器持有类型构造函数的异常安全性、容器是否禁止相同元素的插入等情况,对两者性能进行基准测试。

根据经验,如果下列情况都成立,那么emplace_back几乎总是比push_back的效率更高:

  • 待添加的值是以构造而非赋值方式加入容器,这个大多数标准容器都满足
  • 传递的实参类型与容器持有之物的类型不同,这时emplace_back不要求创建和析构临时对象
  • 容器不太可能由于出现重复情况而拒绝待添加的值,要么容器允许重复值,要么添加的大部分值满足唯一性。因为emplace_back会创建临时节点与容器内值比较,如果因为重复值被拒绝,构造与析构就付出了成本。

除此之外,如果要使用emplace_back,还有两个问题需要处理:

  • 涉及到资源管理。push_back会存在临时对象,当构造抛出异常时,临时对象可以正常析构释放对象
  • 与带有explicit声明饰词的构造函数之间的互动。在使用emplace_back,要特别小心去保证传递了正确的实参,因为即使是带有explicit声明饰词的构造函数也会被编译器纳入考虑范围,因为它会尽力找到某种方法来解释你的代码使它合法(即使不符合逻辑)。

以上,在emplace_back不适合使用的时候,就是push_back的用武之地了。

由于它内在机制的不同,自然也就不会造成那些隐晦的、难以调试的bug。

小结

C11可以使用emplace_back和push_back向容器内添加元素。

建议优先选用emplace_back,因为它几乎能做到push_back所能做到的一切,且可能避免性能上的一些潜在问题。

但emplace_back在一些场景下也有使用陷阱,如异常安全性、能够通过编译但可能导致未定义的行为等,在这些场合下使用push_back就可以避免这些问题。

在具体的使用场景下,选用何种方式来实现,以及真正的性能差异,需要进行基准测试。

参考资料

《Effective Modern C++》

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值