Answers:
在过去的四年中,我已经对这个问题进行了很多思考。我得出的结论是,大多数关于push_back
vs.的解释都emplace_back
没有完整描述。
去年,我在C ++ Now上发表了有关C ++ 14中类型归纳的演讲。我从13:49 开始讨论push_back
vs. emplace_back
,但是在此之前有有用的信息提供了一些支持证据。
真正的主要区别与隐式和显式构造函数有关。请考虑以下情况:只有一个参数要传递给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
调用将仅调用隐式的构造函数。隐式构造函数应该是安全的。如果您可以U
从隐式构造a T
,则表示U
可以T
毫无损失地保存所有信息。在几乎任何情况下,通过a都是安全的,T
如果您U
改为a ,则没人会介意。隐式构造函数的一个很好的例子是从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的成功。相反,我掩盖了该错误,直到几个月后才发现它。
std::unique_ptr<T>
具有来自的显式构造函数T *
。因为emplace_back
可以调用显式构造函数,所以传递非所有者指针可以很好地进行编译。但是,当v
超出范围时,析构函数将尝试调用delete
该指针,该指针未被分配,new
因为它只是一个堆栈对象。这导致未定义的行为。
explicit
构造函数是explicit
应用了关键字的构造函数。“隐式”构造函数是没有该关键字的任何构造函数。在std::unique_ptr
from的构造函数的情况下T *
,的实现者std::unique_ptr
编写了该构造函数,但是这里的问题是这种类型的用户称为emplace_back
,该用户称为该显式构造函数。如果为push_back
,则无需调用该构造函数,而是依靠隐式转换,该转换只能调用隐式构造函数。
push_back
总是允许使用我很喜欢的统一初始化。例如:
struct aggregate {
int foo;
int bar;
};
std::vector<aggregate> v;
v.push_back({ 42, 121 });
另一方面,v.emplace_back({ 42, 121 });
将无法正常工作。
{}
语法来调用实际的构造函数,则只需删除{}
并使用即可emplace_back
。
{}
语法来调用实际的构造函数。您可以提供aggregate
一个使用2个整数的构造函数,并且在使用{}
语法时会调用此构造函数。关键是,如果您尝试调用构造函数,那emplace_back
将是更好的选择,因为它可以就地调用构造函数。因此,不需要类型是可复制的。
emplace
如果不显式编写样例构造函数,我仍然无法编译试图聚合的代码。目前还不清楚是否将其视为缺陷并因此有资格进行反向移植,或者C ++ <20的用户是否仍将保留SoL。
与C ++ 11之前的编译器向后兼容。
push_back
更好的用例。
emplace_back
是不是一个“伟大”的版本push_back
。这是一个潜在的危险版本。阅读其他答案。
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。这是由于以下事实:对2的调用emplace_back
导致调整大小,此时调整大小v[0]
变为无效。
上述代码的有效实现将使用push_back()
而不是emplace_back()
如下所示:
std::vector<int> v;
v.emplace_back(123);
v.push_back(v[0]);
注意:使用整数向量仅用于演示目的。我发现了一个更为复杂的类,这个问题包括动态分配的成员变量以及对的调用emplace_back()
导致了严重的崩溃。
push_back
在这种情况下,有什么不同?