【问题标题】:push_back vs emplace_backpush_back vs emplace_back
【发布时间】:2011-05-17 06:05:24
【问题描述】:

我对@9​​87654324@ 和emplace_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 emulation,而不是 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_backemplace_back 更可取吗?我能想到的唯一情况是,如果一个类在某种程度上是可复制的 (T&operator=(constT&)) 但不可构造 (T(constT&)),但我想不出为什么有人会想要那样。

标签: c++ visual-studio-2010 stl c++11 move-semantics


【解决方案1】:

除了访客所说的:

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

但是emplace_back的真正C++0x形式真的很有用: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&lt;T&gt;(),元组和新的 &lt;functional&gt; 中的东西。这 预处理器机器相对 难以使用和维护。还, 它显着影响编译 速度,因为我们必须反复 包括子标题。由于一个 结合我们的时间限制 和编译速度问题,我们 没有模拟可变参数模板 在我们的 emplace 函数中。

当可变参数模板 在编译器中实现,你可以 期望我们会利用 他们在图书馆,包括在 我们的 emplace 函数。我们采取 非常重视一致性,但是 不幸的是,我们不能做所有事情 一下子。

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

【讨论】:

  • 澄清这是一个 MSVS10 问题,而不是 C++ 问题,这是这里最重要的部分。谢谢。
  • 我相信你的最后一行 C++ 代码行不通。 pair&lt;const int,Complicated&gt; 没有一个构造函数,它接受一个 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
【解决方案2】:

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()。这意味着任何没有前置条件的函数仍然可以被调用(析构函数永远不能有前置条件)。
【解决方案3】:

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

对于emplace_back 构造函数A (int x_arg) 将被调用。而对于 push_back A (int x_arg) 先调用,move A (A &amp;&amp;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会调用move构造函数!
  • @AndreasK。这与 emplace_back 无关,而是扩大了向量的大小。您可以通过打印正在移动的内容来检查,而不仅仅是"A (A &amp;&amp;)\n",打印"A (A &amp;&amp;) on " &lt;&lt; rhs.x &lt;&lt; "\n"。你可以看到它in this edited code snippet
【解决方案4】:

另一个列表示例:

// 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 之后改变对象本身,那么vector中的对象也应该改变,对吧?
  • @MrNobody 和 emplace_back 它是将参数转发给构造函数的容器 - 也就是在调用之前或之后,您手边都没有任何对象。 “改变对象本身”是什么意思?容器中只有对象。或者您事先创建对象 - 在这种情况下,它的作用与 push_back 相同。
【解决方案5】:

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

注意事项:

  1. push_back 在上述情况下会创建一个临时对象并移动它 进入容器。但是,用于emplace_back 的就地构造会更多 比构造然后移动对象(这通常涉及一些复制)更高效。
  2. 一般情况下,您可以在所有情况下使用emplace_back 而不是push_back,这不会有太大问题。 (见exceptions

【讨论】:

    【解决方案6】:

    这里显示了 push_back 和 emplace_back 的一个很好的代码。

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

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

    【讨论】:

      【解决方案7】:

      emplace_back 一致性实现将在添加到向量时将参数转发给 vector&lt;Object&gt;::value_typeconstructor。我记得 Visual Studio 不支持可变参数模板,但 Visual Studio 2013 RC 将支持可变参数模板,所以我猜会添加一个符合要求的签名。

      对于emplace_back,如果您将参数直接转发给vector&lt;Object&gt;::value_type 构造函数,那么严格来说,您不需要为emplace_back 函数提供可移动或可复制的类型。在vector&lt;NonCopyableNonMovableObject&gt; 的情况下,这没有用,因为vector&lt;Object&gt;::value_type 需要一个可复制或可移动的类型来增长。

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

      【讨论】:

        猜你喜欢
        • 2015-01-15
        • 2017-09-21
        • 2020-09-12
        • 2021-10-12
        • 2018-11-15
        • 2014-07-06
        • 2015-05-19
        • 1970-01-01
        相关资源
        最近更新 更多