ChatGPT解决这个技术问题 Extra ChatGPT

push_back vs emplace_back

我对 push_backemplace_back 之间的区别有点困惑。

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

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

请注意(正如 Thomas 在下面所说),问题中的代码来自 MSVS 对 C++0x 的模拟,而不是 C++0x 的实际含义。
更好的阅读论文是:open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2345.pdf。 N2642 主要是标准的措辞; N2345 是解释和激发这个想法的论文。
请注意,即使在 MSVC10 中,也有一个采用 universal referencetemplate <class _Valty> void emplace_back(_Valty&& _Val) 版本,它提供了对 explicit 单参数构造函数的完美转发。
相关:是否存在 push_back 优于 emplace_back 的情况?我能想到的唯一情况是,如果一个类在某种程度上是可复制的(T&operator=(constT&))但不可构造(T(constT&)),但我想不出为什么有人会想要这样。

C
Callum Watkins

除了访客所说的:

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

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

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

这很有用,因为无论 RVO 和移动语义带来多少聪明才智,仍然存在一些复杂的情况,即 push_back 可能会进行不必要的复制(或移动)。例如,使用 std::map 的传统 insert() 函数,您必须创建一个临时文件,然后将其复制到 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++ blog 上问了同样的问题。这是来自微软 Visual C++ 标准库实现的官方维护者 Stephan T Lavavej 的回答。

问:beta 2 emplace 函数现在只是某种占位符吗?答:您可能知道,可变参数模板在 VC10 中没有实现。我们使用预处理器机制模拟它们,例如 make_shared()、元组和 中的新事物。这种预处理器机器相对难以使用和维护。此外,它会显着影响编译速度,因为我们必须重复包含子标题。由于我们的时间限制和编译速度问题的结合,我们没有在我们的 emplace 函数中模拟可变参数模板。当在编译器中实现可变参数模板时,您可以期望我们将在库中利用它们,包括在我们的 emplace 函数中。我们非常重视一致性,但不幸的是,我们不能一次完成所有事情。

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


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

emplace_back 不应采用 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

@David:但是您在范围内有一个移动的 s ,这不是很危险吗?
如果您不再打算使用 s 来实现它的价值,这并不危险。移动不会使 s 无效,移动只会窃取 s 中已经完成的内部内存分配,并使其处于默认状态(未分配字符串),当被破坏时会很好,就像您刚刚键入 std::string str;
@David:我不确定移出的对象是否必须对除后续销毁之外的任何用途有效。
vec.emplace_back("Hello") 将起作用,因为 const char* 参数将被转发string 构造函数。这就是emplace_back的重点。
@BenVoigt:移出对象必须处于有效(但未指定)状态。但是,这并不一定意味着您可以对其执行任何操作。考虑 std::vector。空 std::vector 是有效状态,但您不能对其调用 front()。这意味着任何没有前置条件的函数仍然可以被调用(并且析构函数永远不能有前置条件)。
v
vadikrobot

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;
}

输出:

call emplace_back:
A (x_arg)

call push_back:
A (x_arg)
A (A &&)

我注意到我有调用 v.emplace_back(x); 的代码后来到这里,其中 x 是显式可移动构造但只能显式复制构造。 emplace_back 是“隐式”显式的这一事实使我认为我的附加功能应该是 push_back。想法?
如果您第二次调用 a.emplace_back,将调用移动构造函数!
@AndreasK。这与 emplace_back 无关,而是扩大了向量的大小。您可以通过打印正在移动的内容而不是仅打印 "A (A &&)\n" 来检查是否打印 "A (A &&) on " << rhs.x << "\n"。您可以看到它in this edited code snippet
O
Olivia Stork

列表的另一个示例:

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

// creates a new object and then copies (or moves) that object.
push_back(ExplicitDataType{"element"});

这是否意味着 emplace_back 不会创建副本?它只存储实际对象。如果我们在 emplace_back 之后改变对象本身,向量中的对象也应该改变,对吧?
@MrNobody 与 emplace_back 它是将参数转发给构造函数的容器 - 也就是在调用之前或之后你手头没有任何对象。 “改变对象本身”是什么意思?容器中只有对象。或者您事先创建对象 - 在这种情况下,它的作用与 push_back 相同。
v
vaibhav kumar

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

笔记:

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


D
Dharma

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

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

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


G
Germán Diago

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 与既不可复制也不可移动的映射类型一起使用。