push_back vs emplace_back

ron*_*nag 684 c++ stl visual-studio-2010 move-semantics c++11

我有点困惑有关之间的差异push_backemplace_back.

void emplace_back(Type&& _Val);
void push_back(const Type& _Val);
void push_back(Type&& _Val);
Run Code Online (Sandbox Code Playgroud)

因为有一个push_back超载采用右值参考我不太明白目的是emplace_back什么?

Tho*_*tit 519

除了访客所说的:

void emplace_back(Type&& _Val)MSCV10提供的功能是不符合和冗余的,因为正如您所指出的那样,它完全等同于push_back(Type&& _Val).

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

而不是采用value_type它需要一个可变参数列表,这意味着你现在可以完美地转发参数并直接将一个对象构造成一个容器,而不是一个临时的.

这很有用,因为无论RVO和移动语义多么聪明,还有一些复杂的情况,其中push_back可能会产生不必要的副本(或移动).例如,使用a的传统insert()功能std::map,您必须创建一个临时的,然后将其复制到一个std::pair<Key, Value>,然后将其复制到地图中:

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);
Run Code Online (Sandbox Code Playgroud)

那么为什么他们没有在MSVC中实现正确版本的emplace_back呢?实际上,它在一段时间之前也让我感到烦恼,所以我在Visual C++博客上问了同样的问题.以下是来自Microsoft的Visual C++标准库实现的官方维护者Stephan T Lavavej的答案.

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

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

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

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

  • 澄清这是一个MSVS10问题,而不是C++问题是这里最重要的部分.谢谢. (92认同)
  • 我相信你的最后一行C++代码不起作用.`pair <const int,Complicated>`没有一个构造函数,它接受一个int,另一个是int,一个double,第四个参数是一个字符串.但是,您*可以*使用其分段构造函数直接构造此对对象.语法将是不同的,当然:`m.emplace(std :: piecewise,std :: forward_as_tuple(4),std :: forward_as_tuple(anInt,aDouble,aString));` (11认同)
  • 是否应更新此答案以反映vs2013的新发展? (11认同)
  • 如果你正在使用Visual Studio 2013或更高版本_now_,你应该支持"真正的"`emplace_back`,只要它在添加了可变参数模板时被实现到Visual C++中:https://msdn.microsoft.com/en -us /库/ hh567368.aspx (6认同)
  • 令人高兴的可变参数模板将在VS2013中,现在处于预览状态. (3认同)

vis*_*tor 187

emplace_back不应该采用类型的参数vector::value_type,而是转发到附加项的构造函数的可变参数.

template <class... Args> void emplace_back(Args&&... args); 
Run Code Online (Sandbox Code Playgroud)

可以传递value_type将被转发到复制构造函数的文件.

因为它转发参数,这意味着如果你没有rvalue,这仍然意味着容器将存储"复制"副本,而不是移动副本.

 std::vector<std::string> vec;
 vec.emplace_back(std::string("Hello")); // moves
 std::string s;
 vec.emplace_back(s); //copies
Run Code Online (Sandbox Code Playgroud)

但是上面的内容应该与之相同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
Run Code Online (Sandbox Code Playgroud)

  • `vec.emplace_back("Hello")`将起作用,因为`const char*`参数将*转发*到`string`构造函数.这是`emplace_back`的重点. (44认同)
  • @BenVoigt:移动的对象需要处于有效(但未指定)的状态.但这并不一定意味着您可以对其执行任何操作.考虑`std :: vector`.空的`std :: vector`是一个有效的状态,但你不能在它上面调用`front()`.这意味着仍然可以调用任何没有前置条件的函数(并且析构函数永远不会有前置条件). (8认同)
  • @David:我不确定移动对象是否需要对除了后续破坏之外的任何用途有效. (4认同)
  • @David:但是你在范围内有一个感动的,不是那么危险吗? (2认同)
  • 如果您不打算再使用s值,那么这并不危险.移动不会使s无效,移动只会窃取已经在s中完成的内部内存分配,并使其处于默认状态(没有分配sting),当被破坏时,就好像你刚刚输入了std :: string str一样; (2认同)

vad*_*bot 73

优化emplace_back可以在下一个例子中证明.

对于emplace_back构造函数A (int x_arg)将被调用.并且 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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

call emplace_back:
A (x_arg)

call push_back:
A (x_arg)
A (A &&)
Run Code Online (Sandbox Code Playgroud)

  • 代码示例的+1,用于演示调用`emplace_back` vs`push_back`时实际发生的情况. (15认同)
  • @AndreasK。这与 emplace_back 无关,而是扩展向量的大小。您可以通过打印正在移动的内容来检查,而不仅仅是打印 `"A (A &amp;&amp;)\n"`,打印 `"A (A &amp;&amp;) on " &lt;&lt; rhs.x &lt;&lt; "\n"`。您可以[在此编辑的代码片段中](https://www.mycompiler.io/view/FSiSqRh)看到它。 (2认同)

Dha*_*rma 9

这里显示了push_back和emplace_back的漂亮代码。

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

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


Ger*_*ago 8

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>,因为一旦在地图中分配了一个条目,它就不再需要被移动或复制了vector,这意味着你可以std::map有效地使用既不可复制也不能复制的映射类型.活动.


vai*_*mar 8

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

笔记:

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


小智 7

如果是列表,还需要一个:

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


//It will create new object and then copy(or move) its value of arguments.
push_back(explicitDataType{"element"});
Run Code Online (Sandbox Code Playgroud)

  • 这是否意味着 emplace_back 不创建副本?它只存储实际的对象。如果我们在 emplace_back 之后更改对象本身,则向量中的对象也应该更改,对吗? (5认同)