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++》
推荐阅读
-
C++ 中emplace_back和push_back差异
-
[C++] push_back vs emplace_back
-
C++11:右值引用、移动构造、std::move, 以及使用emplace_back代替push_back
-
c++11中emplace_back vs push_back
-
C++11 之 emplace_back() 与 push_back() 的区别
-
c++11 之emplace_back 与 push_back的区别
-
C++之emplace_back() VS push_back()
-
c++11新特性(7)之push_back与emplace_back之间的区别
-
vector中push_back和emplace_back的区别
-
emplace_back VS push_back