990

我对push_back和之间的区别有点困惑emplace_back

void emplace_back(Type&& _Val);
void push_back(const Type& _Val);
void push_back(Type&& _Val);

由于存在push_back采用右值引用的重载,我不太明白这样做的目的是emplace_back什么?

4

7 回答 7

708

除了访客所说的:

MSCV10 提供的功能void emplace_back(Type&& _Val)不符合要求且是多余的,因为正如您所指出的,它严格等同于push_back(Type&& _Val).

但是真正的 C++0x 形式emplace_back真的很有用void emplace_back(Args&&...)

而不是value_type采用一个可变参数列表,这意味着您现在可以完美地转发参数并将对象直接构造到容器中,而根本不需要临时参数。

这很有用,因为无论 RVO 和移动语义带来多少聪明才智,仍然存在一些复杂的情况,即 push_back 可能会进行不必要的复制(或移动)。例如,使用insert()a 的传统功能std::map,您必须创建一个临时文件,然后将其复制到 astd::pair<Key, Value>中,然后将其复制到 map 中:

std::map<int, Complicated> m;
int anInt = 4;
double aDouble = 5.0;
std::string aString = "C++";

// cross your finger so that the optimizer is really good
m.insert(std::make_pair(4, Complicated(anInt, aDouble, aString))); 

// should be easier for the optimizer
m.emplace(4, anInt, aDouble, aString);

那么为什么他们没有在 MSVC 中实现正确版本的 emplace_back 呢?实际上,它之前也困扰过我,所以我在Visual C++ 博客上问了同样的问题。这是来自微软 Visual C++ 标准库实现的官方维护者 Stephan T Lavavej 的回答。

问:beta 2 emplace 函数现在只是某种占位符吗?

答:您可能知道,可变参数模板在 VC10 中没有实现。我们使用预处理器来模拟它们,比如 make_shared<T>()tuple 和<functional>. 这种预处理器机器相对难以使用和维护。此外,它会显着影响编译速度,因为我们必须重复包含子标题。由于我们的时间限制和编译速度问题的结合,我们没有在我们的 emplace 函数中模拟可变参数模板。

当在编译器中实现可变参数模板时,您可以期望我们将在库中利用它们,包括在我们的 emplace 函数中。我们非常重视一致性,但不幸的是,我们不能一次完成所有事情。

这是一个可以理解的决定。每个尝试过一次用预处理器可怕的技巧模拟可变参数模板的人都知道这些东西有多恶心。

于 2010-11-29T18:00:28.900 回答
238

emplace_back不应采用 type 的参数vector::value_type,而是转发给附加项的构造函数的可变参数。

template <class... Args> void emplace_back(Args&&... args); 

可以传递value_type将转发给复制构造函数的 a。

因为它转发参数,这意味着如果您没有右值,这仍然意味着容器将存储“复制”副本,而不是移动副本。

 std::vector<std::string> vec;
 vec.emplace_back(std::string("Hello")); // moves
 std::string s;
 vec.emplace_back(s); //copies

但上述内容应该与push_back所做的相同。它可能更适用于以下用例:

 std::vector<std::pair<std::string, std::string> > vec;
 vec.emplace_back(std::string("Hello"), std::string("world")); 
 // should end up invoking this constructor:
 //template<class U, class V> pair(U&& x, V&& y);
 //without making any copies of the strings
于 2010-11-29T12:47:56.663 回答
125

emplace_back可以在下一个示例中演示优化。

对于emplace_back构造函数A (int x_arg)将被调用。并且 for push_back A (int x_arg)被首先move A (A &&rhs)调用,然后被调用。

当然,构造函数必须标记为explicit,但对于当前示例来说,删除显式性很好。

#include <iostream>
#include <vector>
class A
{
public:
  A (int x_arg) : x (x_arg) { std::cout << "A (x_arg)\n"; }
  A () { x = 0; std::cout << "A ()\n"; }
  A (const A &rhs) noexcept { x = rhs.x; std::cout << "A (A &)\n"; }
  A (A &&rhs) noexcept { x = rhs.x; std::cout << "A (A &&)\n"; }

private:
  int x;
};

int main ()
{
  {
    std::vector<A> a;
    std::cout << "call emplace_back:\n";
    a.emplace_back (0);
  }
  {
    std::vector<A> a;
    std::cout << "call push_back:\n";
    a.push_back (1);
  }
  return 0;
}

输出:

call emplace_back:
A (x_arg)

call push_back:
A (x_arg)
A (A &&)
于 2015-10-27T16:42:56.627 回答
18

列表的另一个示例:

// constructs the elements in place.                                                
emplace_back("element");

// creates a new object and then copies (or moves) that object.
push_back(ExplicitDataType{"element"});
于 2017-12-28T10:08:56.923 回答
14

的特定用例emplace_back:如果您需要创建一个临时对象,然后将其推送到容器中,请使用emplace_back而不是push_back. 它将在容器内就地创建对象。

笔记:

  1. push_back在上述情况下将创建一个临时对象并将其移动到容器中。但是,用于 的就地构造emplace_back比构造然后移动对象(这通常涉及一些复制)具有更高的性能。
  2. 通常,您可以在所有情况下使用emplace_back而不是push_back没有太大问题。(见例外
于 2019-12-13T23:14:41.540 回答
10

此处显示了 push_back 和 emplace_back 的一个很好的代码。

http://en.cppreference.com/w/cpp/container/vector/emplace_back

您可以在 push_back 而不是 emplace_back 上看到移动操作。

于 2018-02-27T14:26:20.337 回答
9

emplace_back符合要求的实现将vector<Object>::value_type在添加到向量时将参数转发给构造函数。我记得 Visual Studio 不支持可变参数模板,但是 Visual Studio 2013 RC 将支持可变参数模板,所以我想会添加一个符合要求的签名。

使用emplace_back,如果您将参数直接转发给构造函数,则严格来说vector<Object>::value_type,您不需要一个可移动或可复制的类型。emplace_back在这种vector<NonCopyableNonMovableObject>情况下,这没有用,因为vector<Object>::value_type 需要可复制或可移动的类型来增长。

请注意,这可能对 有用std::map<Key, NonCopyableNonMovableObject>,因为一旦您在映射中分配了一个条目,就不再需要移动或复制它,这与 with 不同vector,这意味着您可以std::map有效地使用既不可复制也不可复制的映射类型活动。

于 2013-09-24T04:39:05.270 回答