为什么我会用push_back而不是emplace_back
Why would I ever use push_back instead of emplace_back?
C++11 向量具有新的函数emplace_back
。与依赖于编译器优化来避免复制的push_back
不同,emplace_back
使用完美转发将参数直接发送到构造函数以就地创建对象。在我看来,emplace_back
做了push_back
能做的一切,但有时它会做得更好(但永远不会更糟)。
我有什么理由使用push_back
?
四年里,我对这个问题进行了相当多的思考。我得出的结论是,大多数关于push_back
与 emplace_back
错过了全貌。
去年,我在C++14 C++Now上做了一个关于类型演绎的演讲。我开始谈论push_back
与 emplace_back
在13:49,但在此之前有一些有用的信息提供了一些支持证据。
真正的主要区别与隐式构造函数与显式构造函数有关。考虑一下我们有一个参数的情况,我们想传递给push_back
或emplace_back
。
std::vector<T> v;
v.push_back(x);
v.emplace_back(x);
优化编译器掌握了这一点后,这两个语句在生成的代码方面没有区别。传统智慧是,push_back
将构造一个临时对象,然后将其移动到v
,而emplace_back
将推进论点并直接在原地构建它,而无需复制或移动。根据标准库中编写的代码,这可能是正确的,但它错误地假设优化编译器的工作是生成您编写的代码。优化编译器的工作实际上是生成代码,如果你是特定于平台的优化专家,并且不关心可维护性,只关心性能,你会写的代码。
这两个语句之间的实际区别在于,更强大的emplace_back
将调用任何类型的构造函数,而更谨慎的push_back
将仅调用隐式构造函数。隐式构造函数应该是安全的。如果你能从T
隐式构造一个U
,你就是在说U
可以把所有的信息都保存在T
而不会丢失。几乎在任何情况下通过T
都是安全的,没有人会介意你把它变成U
。隐式构造函数的一个很好的例子是从 std::uint32_t
到 std::uint64_t
的转换。隐式转换的一个不好的例子是double
std::uint8_t
。
我们希望在编程中保持谨慎。我们不想使用强大的功能,因为功能越强大,就越容易意外地做一些不正确或意外的事情。如果您打算调用显式构造函数,则需要 emplace_back
的强大功能。如果只想调用隐式构造函数,请坚持使用push_back
的安全性。
一个例子
std::vector<std::unique_ptr<T>> v;
T a;
v.emplace_back(std::addressof(a)); // compiles
v.push_back(std::addressof(a)); // fails to compile
std::unique_ptr<T>
有一个来自T *
的显式构造函数。因为emplace_back
可以调用显式构造函数,所以传递一个非拥有指针就可以了。但是,当v
超出范围时,析构函数将尝试在该指针上调用delete
,该指针不是由new
分配的,因为它只是一个堆栈对象。这会导致未定义的行为。
这不仅仅是发明的代码。这是我遇到的一个真正的生产错误。代码是std::vector<T *>
的,但它拥有内容。作为迁移到 C++11 的一部分,我正确地将T *
更改为 std::unique_ptr<T>
,以指示向量拥有其内存。然而,我基于我在 2012 年的理解来做出这些更改,在此期间我认为"emplace_back
做了push_back
能做的一切,甚至更多,所以我为什么要使用push_back
?",所以我也把push_back
改为emplace_back
。
如果我将代码保留为使用更安全的push_back
,我会立即发现这个长期存在的错误,它会被视为升级到 C++11 的成功。相反,我屏蔽了这个错误,直到几个月后才找到它。
push_back
总是允许使用统一初始化,我非常喜欢。例如:
struct aggregate {
int foo;
int bar;
};
std::vector<aggregate> v;
v.push_back({ 42, 121 });
另一方面,v.emplace_back({ 42, 121 });
不起作用。
向后兼容 C++11 之前的编译器。
emplace_back的某些库实现的行为不符合C++标准中指定的行为,包括 Visual Studio 2012、2013 和 2015 附带的版本。
为了适应已知的编译器错误,如果参数引用迭代器或其他在调用后无效的对象,则首选使用 std::vector::push_back()
。
std::vector<int> v;
v.emplace_back(123);
v.emplace_back(v[0]); // Produces incorrect results in some compilers
在一个编译器上,v 包含值 123 和 21,而不是预期的 123 和 123。这是因为第二次调用emplace_back
会导致调整大小,此时v[0]
将变为无效。
上述代码的工作实现将使用push_back()
而不是emplace_back()
,如下所示:
std::vector<int> v;
v.emplace_back(123);
v.push_back(v[0]);
注意:使用 ints 向量是为了演示目的。我在一个更复杂的类中发现了这个问题,该类包括动态分配的成员变量,并且对emplace_back()
的调用导致了硬崩溃。
仅将push_back
用于基元/内置类型或原始指针。否则使用 emplace_back
.
考虑使用 C++-17 编译器在 Visual Studio 2019 中发生的情况。我们emplace_back了一个设置了适当参数的函数。然后有人更改了emplace_back调用的构造器的参数。VS中没有警告,代码也编译良好,然后在运行时崩溃。在此之后,我从代码库中删除了所有emplace_back。
- TMap::Emplace() 在应用现有密钥时会覆盖吗?
- 推导 std::vector::back() 的返回类型
- 为什么 C++ std::unordered_map 从 emplace/ 找到返回一个迭代器?
- vector.back() 和 vector[vector.size() - 1] 之间的区别?
- 将 std::map::emplace 与返回 shared_ptr 的函数一起使用是否正确?
- vector.push_back(vector.back()+1) 是未定义的行为吗?
- 不为 emplace() 定义构造函数的解决方法
- 如何提取由 unordered_map::emplace 重新调整的货币对的值?
- 如何最好地将 emplace 与 std::map 一起使用
- 线路抑制状态错误 C4703 可能未初始化的局部指针变量"back"已使用
- std::vector using back(), pop_back(), push_back(), 得到'double free or corruption'错误
- std::vector::emplace() 真的在面对抛出移动构造函数/赋值运算符时提供了强大的异常保证吗?
- std::map::emplace无法解决,但插入右值有效 - 为什么?
- vector.back()和vector.end()有什么区别
- 为什么 std::map emplace 需要在 gcc 上有一个复制构造函数
- 为什么unordered_map的 emplace piecewise_construct参数需要默认构造函数?
- std::元组到元组的映射和使用 emplace
- 列表大小为 1,但 front() 和 back() 不相等
- 如何使用 emplace 添加到C++中的地图
- C++ map emplace