为什么我要使用push_back而不是emplace_back?

时间:2022-03-10 04:15:54

C++11 vectors have the new function emplace_back. Unlike push_back, which relies on compiler optimizations to avoid copies, emplace_back uses perfect forwarding to send the arguments directly to the constructor to create an object in-place. It seems to me that emplace_back does everything push_back can do, but some of the time it will do it better (but never worse).

c++ 11向量有了新的函数emplace_back。与push_back不同,它依赖于编译器优化来避免复制,emplace_back使用完美的转发将参数直接发送到构造函数,以创建一个对象。在我看来,emplace_back做了push_back能做的一切,但有时它会做得更好(但永远不会更糟)。

What reason do I have to use push_back?

我为什么要使用push_back?

4 个解决方案

#1


100  

push_back always allows the use of uniform initialization, which I'm very fond of. For instance:

push_back总是允许使用统一的初始化,这是我非常喜欢的。例如:

struct aggregate {
    int foo;
    int bar;
};

std::vector<aggregate> v;
v.push_back({ 42, 121 });

On the other hand, v.emplace_back({ 42, 121 }); will not work.

另一方面,v。emplace_back({ 121 });将不会工作。

#2


77  

Backwards compatibility with pre-C++11 compilers.

前c++ 11编译器的向后兼容性。

#3


76  

I have thought about this question quite a bit over the past four years. I have come to the conclusion that most explanations about push_back vs. emplace_back miss the full picture.

在过去的四年里,我对这个问题想了很多。我得出的结论是,关于push_back和emplace_back的大多数解释都忽略了完整的图片。

Last year, I gave a presentation at C++Now on Type Deduction in C++14. I start talking about push_back vs. emplace_back at 13:49, but there is useful information that provides some supporting evidence prior to that.

去年,我在c++ Now做了一个关于c++ 14的类型演绎的演讲。我从13:49开始讨论push_back和emplace_back,但是有一些有用的信息可以在此之前提供一些支持的证据。

The real primary difference has to do with implicit vs. explicit constructors. Consider the case where we have a single argument that we want to pass to push_back or emplace_back.

真正的主要区别在于隐式构造函数和显式构造函数。考虑这样的情况,我们有一个参数,我们希望传递给push_back或emplace_back。

std::vector<T> v;
v.push_back(x);
v.emplace_back(x);

After your optimizing compiler gets its hands on this, there is no difference between these two statements in terms of generated code. The traditional wisdom is that push_back will construct a temporary object, which will then get moved into v whereas emplace_back will forward the argument along and construct it directly in place with no copies or moves. This may be true based on the code as written in standard libraries, but it makes the mistaken assumption that the optimizing compiler's job is to generate the code you wrote. The optimizing compiler's job is actually to generate the code you would have written if you were an expert on platform-specific optimizations and did not care about maintainability, just performance.

在您的优化编译器得到它的手之后,这两个语句在生成代码方面没有区别。传统的智慧是push_back将构造一个临时对象,然后将其移动到v中,而emplace_back将向前移动参数,并在没有复制或移动的情况下直接构建它。基于在标准库中编写的代码,这可能是正确的,但它错误地假设优化编译器的工作是生成您编写的代码。优化编译器的工作实际上是生成代码,如果您是特定于平台的优化专家,并且不关心可维护性,只关心性能。

The actual difference between these two statements is that the more powerful emplace_back will call any type of constructor out there, whereas the more cautious push_back will call only constructors that are implicit. Implicit constructors are supposed to be safe. If you can implicitly construct a U from a T, you are saying that U can hold all of the information in T with no loss. It is safe in pretty much any situation to pass a T and no one will mind if you make it a U instead. A good example of an implicit constructor is the conversion from std::uint32_t to std::uint64_t. A bad example of an implicit conversion is double to std::uint8_t.

这两个语句的实际区别在于,更强大的emplace_back将调用任何类型的构造函数,而更谨慎的push_back将只调用隐式的构造函数。隐式构造函数应该是安全的。如果你可以隐式地从T构造U,你是说U可以在T中保存所有的信息而不损失。在任何情况下通过T都是安全的,没有人会介意你把它变成U。隐式构造函数的一个很好的例子是从std::uint32_t到std::uint64_t的转换。隐式转换的一个不好的例子是双重的:uint8_t。

We want to be cautious in our programming. We do not want to use powerful features because the more powerful the feature, the easier it is to accidentally do something incorrect or unexpected. If you intend to call explicit constructors, then you need the power of emplace_back. If you want to call only implicit constructors, stick with the safety of push_back.

我们在编程时要谨慎。我们不希望使用强大的功能,因为功能越强大,就越容易意外地做一些不正确或意想不到的事情。如果您打算调用显式构造函数,那么需要emplace_back的功能。如果您想只调用隐式构造函数,请坚持push_back的安全性。

An example

一个例子

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> has an explicit constructor from T *. Because emplace_back can call explicit constructors, passing a non-owning pointer compiles just fine. However, when v goes out of scope, the destructor will attempt to call delete on that pointer, which was not allocated by new because it is just a stack object. This leads to undefined behavior.

unique_ptr 有一个来自T *的显式构造函数。因为emplace_back可以调用显式构造函数,所以传递一个非拥有的指针编译就可以了。但是,当v超出范围时,析构函数将尝试调用指针上的delete,这个指针不是由new分配的,因为它只是一个堆栈对象。这会导致未定义的行为。

This is not just invented code. This was a real production bug I encountered. The code was std::vector<T *>, but it owned the contents. As part of the migration to C++11, I correctly changed T * to std::unique_ptr<T> to indicate that the vector owned its memory. However, I was basing these changes off my understanding in 2012, during which I thought "emplace_back does everything push_back can do and more, so why would I ever use push_back?", so I also changed the push_back to emplace_back.

这不仅仅是发明的代码。这是我遇到的一个真正的生产错误。代码是std::vector ,但它拥有内容。在迁移到c++ 11的过程中,我正确地将T *改为std: unique_ptr ,以表明向量拥有它的内存。但是,我在2012年的时候,基于我的理解,我认为“emplace_back做了所有push_back都能做的事情,所以为什么我要使用push_back呢?”,因此我也将push_back更改为emplace_back。

Had I instead left the code as using the safer push_back, I would have instantly caught this long-standing bug and it would have been viewed as a success of upgrading to C++11. Instead, I masked the bug and didn't find it until months later.

如果我将代码作为使用更安全的push_back来替换,我将立即发现这个长期存在的bug,并将其视为升级到c++ 11的成功。相反,我掩盖了这个漏洞,直到几个月后才发现它。

#4


62  

Some library implementations of emplace_back do not behave as specified in the C++ standard including the version that ship with Visual Studio 2012, 2013 and 2015.

emplace_back的一些库实现不像c++标准中指定的那样,包括Visual Studio 2012、2013和2015的版本。

In order to accommodate known compiler bugs, prefer usingstd::vector::push_back() if the parameters reference iterators or other objects which will be invalid after the call.

为了适应已知的编译器错误,如果参数引用迭代器或其他在调用后无效的对象,请使用ingstd::vector::push_back()。

std::vector<int> v;
v.emplace_back(123);
v.emplace_back(v[0]); // Produces incorrect results in some compilers

On one compiler, v contains the values 123 and 21 instead of the expected 123 and 123. This is due to the fact that the 2nd call to emplace_back results in a resize at which point v[0] becomes invalid.

在一个编译器上,v包含值123和21,而不是期望的123和123。这是由于对emplace_back的第二次调用导致了在v[0]变得无效时的重新调整。

A working implementation of the above code would use push_back() instead of emplace_back() as follows:

上述代码的一个有效实现将使用push_back()而不是emplace_back(),如下所示:

std::vector<int> v;
v.emplace_back(123);
v.push_back(v[0]);

Note: The use of a vector of ints is for demonstration purposes. I discovered this issue with a much more complex class which included dynamically allocated member variables and the call to emplace_back() resulted in a hard crash.

注意:ints向量的使用是为了演示目的。我发现了一个更复杂的类,它包含动态分配的成员变量,对emplace_back()的调用导致了硬崩溃。

#1


100  

push_back always allows the use of uniform initialization, which I'm very fond of. For instance:

push_back总是允许使用统一的初始化,这是我非常喜欢的。例如:

struct aggregate {
    int foo;
    int bar;
};

std::vector<aggregate> v;
v.push_back({ 42, 121 });

On the other hand, v.emplace_back({ 42, 121 }); will not work.

另一方面,v。emplace_back({ 121 });将不会工作。

#2


77  

Backwards compatibility with pre-C++11 compilers.

前c++ 11编译器的向后兼容性。

#3


76  

I have thought about this question quite a bit over the past four years. I have come to the conclusion that most explanations about push_back vs. emplace_back miss the full picture.

在过去的四年里,我对这个问题想了很多。我得出的结论是,关于push_back和emplace_back的大多数解释都忽略了完整的图片。

Last year, I gave a presentation at C++Now on Type Deduction in C++14. I start talking about push_back vs. emplace_back at 13:49, but there is useful information that provides some supporting evidence prior to that.

去年,我在c++ Now做了一个关于c++ 14的类型演绎的演讲。我从13:49开始讨论push_back和emplace_back,但是有一些有用的信息可以在此之前提供一些支持的证据。

The real primary difference has to do with implicit vs. explicit constructors. Consider the case where we have a single argument that we want to pass to push_back or emplace_back.

真正的主要区别在于隐式构造函数和显式构造函数。考虑这样的情况,我们有一个参数,我们希望传递给push_back或emplace_back。

std::vector<T> v;
v.push_back(x);
v.emplace_back(x);

After your optimizing compiler gets its hands on this, there is no difference between these two statements in terms of generated code. The traditional wisdom is that push_back will construct a temporary object, which will then get moved into v whereas emplace_back will forward the argument along and construct it directly in place with no copies or moves. This may be true based on the code as written in standard libraries, but it makes the mistaken assumption that the optimizing compiler's job is to generate the code you wrote. The optimizing compiler's job is actually to generate the code you would have written if you were an expert on platform-specific optimizations and did not care about maintainability, just performance.

在您的优化编译器得到它的手之后,这两个语句在生成代码方面没有区别。传统的智慧是push_back将构造一个临时对象,然后将其移动到v中,而emplace_back将向前移动参数,并在没有复制或移动的情况下直接构建它。基于在标准库中编写的代码,这可能是正确的,但它错误地假设优化编译器的工作是生成您编写的代码。优化编译器的工作实际上是生成代码,如果您是特定于平台的优化专家,并且不关心可维护性,只关心性能。

The actual difference between these two statements is that the more powerful emplace_back will call any type of constructor out there, whereas the more cautious push_back will call only constructors that are implicit. Implicit constructors are supposed to be safe. If you can implicitly construct a U from a T, you are saying that U can hold all of the information in T with no loss. It is safe in pretty much any situation to pass a T and no one will mind if you make it a U instead. A good example of an implicit constructor is the conversion from std::uint32_t to std::uint64_t. A bad example of an implicit conversion is double to std::uint8_t.

这两个语句的实际区别在于,更强大的emplace_back将调用任何类型的构造函数,而更谨慎的push_back将只调用隐式的构造函数。隐式构造函数应该是安全的。如果你可以隐式地从T构造U,你是说U可以在T中保存所有的信息而不损失。在任何情况下通过T都是安全的,没有人会介意你把它变成U。隐式构造函数的一个很好的例子是从std::uint32_t到std::uint64_t的转换。隐式转换的一个不好的例子是双重的:uint8_t。

We want to be cautious in our programming. We do not want to use powerful features because the more powerful the feature, the easier it is to accidentally do something incorrect or unexpected. If you intend to call explicit constructors, then you need the power of emplace_back. If you want to call only implicit constructors, stick with the safety of push_back.

我们在编程时要谨慎。我们不希望使用强大的功能,因为功能越强大,就越容易意外地做一些不正确或意想不到的事情。如果您打算调用显式构造函数,那么需要emplace_back的功能。如果您想只调用隐式构造函数,请坚持push_back的安全性。

An example

一个例子

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> has an explicit constructor from T *. Because emplace_back can call explicit constructors, passing a non-owning pointer compiles just fine. However, when v goes out of scope, the destructor will attempt to call delete on that pointer, which was not allocated by new because it is just a stack object. This leads to undefined behavior.

unique_ptr 有一个来自T *的显式构造函数。因为emplace_back可以调用显式构造函数,所以传递一个非拥有的指针编译就可以了。但是,当v超出范围时,析构函数将尝试调用指针上的delete,这个指针不是由new分配的,因为它只是一个堆栈对象。这会导致未定义的行为。

This is not just invented code. This was a real production bug I encountered. The code was std::vector<T *>, but it owned the contents. As part of the migration to C++11, I correctly changed T * to std::unique_ptr<T> to indicate that the vector owned its memory. However, I was basing these changes off my understanding in 2012, during which I thought "emplace_back does everything push_back can do and more, so why would I ever use push_back?", so I also changed the push_back to emplace_back.

这不仅仅是发明的代码。这是我遇到的一个真正的生产错误。代码是std::vector ,但它拥有内容。在迁移到c++ 11的过程中,我正确地将T *改为std: unique_ptr ,以表明向量拥有它的内存。但是,我在2012年的时候,基于我的理解,我认为“emplace_back做了所有push_back都能做的事情,所以为什么我要使用push_back呢?”,因此我也将push_back更改为emplace_back。

Had I instead left the code as using the safer push_back, I would have instantly caught this long-standing bug and it would have been viewed as a success of upgrading to C++11. Instead, I masked the bug and didn't find it until months later.

如果我将代码作为使用更安全的push_back来替换,我将立即发现这个长期存在的bug,并将其视为升级到c++ 11的成功。相反,我掩盖了这个漏洞,直到几个月后才发现它。

#4


62  

Some library implementations of emplace_back do not behave as specified in the C++ standard including the version that ship with Visual Studio 2012, 2013 and 2015.

emplace_back的一些库实现不像c++标准中指定的那样,包括Visual Studio 2012、2013和2015的版本。

In order to accommodate known compiler bugs, prefer usingstd::vector::push_back() if the parameters reference iterators or other objects which will be invalid after the call.

为了适应已知的编译器错误,如果参数引用迭代器或其他在调用后无效的对象,请使用ingstd::vector::push_back()。

std::vector<int> v;
v.emplace_back(123);
v.emplace_back(v[0]); // Produces incorrect results in some compilers

On one compiler, v contains the values 123 and 21 instead of the expected 123 and 123. This is due to the fact that the 2nd call to emplace_back results in a resize at which point v[0] becomes invalid.

在一个编译器上,v包含值123和21,而不是期望的123和123。这是由于对emplace_back的第二次调用导致了在v[0]变得无效时的重新调整。

A working implementation of the above code would use push_back() instead of emplace_back() as follows:

上述代码的一个有效实现将使用push_back()而不是emplace_back(),如下所示:

std::vector<int> v;
v.emplace_back(123);
v.push_back(v[0]);

Note: The use of a vector of ints is for demonstration purposes. I discovered this issue with a much more complex class which included dynamically allocated member variables and the call to emplace_back() resulted in a hard crash.

注意:ints向量的使用是为了演示目的。我发现了一个更复杂的类,它包含动态分配的成员变量,对emplace_back()的调用导致了硬崩溃。