push_back和emplace_back


761

我对push_back和之间的区别感到困惑emplace_back

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

由于有一个push_back重载引用右值引用,我不太明白它的目的是emplace_back什么?



16
请注意(如托马斯在下面所述),问题中的代码来自MSVS 对C ++ 0x 的仿真,而不是C ++ 0x的实际含义。
me22 2010年

5
更好阅读的论文是:open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2345.pdf。N2642主要是该标准的措辞;N2345是解释和激发这种想法的论文。
艾伦(Alan)

请注意,即使在MSVC10中,也有一个template <class _Valty> void emplace_back(_Valty&& _Val)采用通用引用的版本,该版本提供了向explicit单参数构造函数的完美转发。
joki

相关:在任何情况下都比push_back更好emplace_back?我能想到的唯一情况是,一个类是否可以以某种方式复制(T&operator=(constT&))但不能以构造方式(T(constT&)),但是我无法想到为什么有人会想要它。

Answers:


568

访客除了说什么:

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,您必须创建一个临时,然后将其复制到a中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);

那么,为什么他们没有在MSVC中实现正确版本的emplace_back?实际上,它在一段时间之前困扰着我,因此我在Visual C ++博客上提出了同样的问题。这是Microsoft Visual C ++标准库实现的官方维护者Stephan T Lavavej的回答。

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

答:您可能知道,可变参数模板未在VC10中实现。我们使用预处理器机器对它们进行仿真,以处理诸如 make_shared<T>(),元组和中的新事物<functional>。这种预处理器机器相对难以使用和维护。另外,由于我们不得不反复包含子标题,因此它会显着影响编译速度。由于时间限制和编译速度方面的考虑,我们没有在emplace函数中模拟可变参数模板。

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

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


101
澄清这是MSVS10问题,而不是C ++问题是这里最重要的部分。谢谢。
me22 2010年

11
我相信您最后的C ++代码行将无法正常工作。pair<const int,Complicated>没有构造函数,该构造函数需要一个int,另一个int,一个double和一个作为字符串的第4个参数。但是,您可以使用其分段构造直接构造此对对象。当然,语法会有所不同:m.emplace(std::piecewise,std::forward_as_tuple(4),std::forward_as_tuple(anInt,aDouble,aString));
sellibitze 2013年

3
愉快的可变参数模板将在VS2013中发布,现在可以预览。
Daniel Earwicker 2013年

11
是否应更新此答案以反映vs2013中的新发展?
becko 2014年

6
如果您是使用Visual Studio 2013或更高版本,现在,你应该有一个“真实”的支持emplace_back,只要它被实施到Visual C ++时加可变参数模板:msdn.microsoft.com/en-us/library/hh567368。 aspx
kayleeFrye_onDeck

200

emplace_back不应使用type的参数vector::value_type,而应将可变参数传递给附加项目的构造函数。

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

可以传递value_type将转发给副本构造函数的。

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

 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

2
@David:但是您的s范围已经变了,不是很危险吗?
Matthieu M.

2
如果您不打算再使用s作为值,这并不危险。移动不会使s无效,该移动只会窃取已经在s中完成的内部内存分配,并将其保留为默认状态(未分配任何字符串),这在销毁时会很好,就像您刚刚键入std :: string str;一样。
David

4
@David:我不确定是否必须将移出的对象对任何用途都有效,但随后的销毁除外。
Ben Voigt 2010年

46
vec.emplace_back("Hello")将起作用,因为const char*参数将转发string构造函数。这就是重点emplace_back
Alexandre C.

8
@BenVoigt:要求将移出的对象置于有效(但未指定)状态。但是,这不一定意味着您可以对其执行任何操作。考虑一下std::vector。空std::vector为有效状态,但是您不能对其进行调用front()。这意味着仍然可以调用任何没有先决条件的函数(析构函数永远不能有先决条件)。
大卫·斯通

94

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 &&)

21
该代码示例的+1说明了调用emplace_backvs 时实际发生的情况push_back
肖恩

我来到这里是在注意到我的代码正在调用的v.emplace_back(x);地方,其中x是显式可移动构造的,但只能显式复制可构造的。emplace_back“隐式”显式的事实使我认为附加的转到函数应该是push_back。有什么想法吗?

如果您a.emplace_back第二次调用,将调用move构造函数!
XÆA-12


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有效地使用不可复制或映射的映射类型活动。


8

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

// 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"});

1

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

笔记:

  1. push_back在上述情况下,将创建一个临时对象并将其移入容器。但是,用于原位构建的emplace_back性能要比先构建然后移动对象(通常涉及一些复制)的性能更高。
  2. 通常,您可以使用emplace_back而不是push_back在所有情况下都没问题。(请参阅例外
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.