emplace_back() vs push_back 将一对插入 std::vector

emplace_back() vs push_back when inserting a pair into std::vector

我定义了以下内容

std::vector<std::pair<int,int> > my_vec;
my_vec.push_back( {1,2} ); //this works
my_vec.emplace_back( {1,2} ); // this doesn't work
std::pair<int,int> temp_pair = {1,2}; 
my_vec.emplace_back( temp_pair );         //this works

我正在用 c++11 编译。第三行有问题,但我认为你可以在任何有 push_back() 的地方使用 emplace_back(),但这显然是错误的。为什么第三行不行?

emplace_back 将可变参数包作为参数:

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

当你这样称呼它时:emplace_back({1, 2}) 你用一个参数调用它,即 {1, 2} 并且不能推导出 Args。那是因为语言是如何进化的。在 C++ 中 {1, 2} 没有类型。它是一个大括号括起来的初始化列表,可用于某些类型的初始化,但都需要知道初始化的类型。这就是 temp_pair = {1,2}; 起作用的原因,因为 temp_pair 的类型是已知的,并且具有匹配 (int, int).

的构造函数

无论如何 emplace_back 不应该那样使用,而是像这样使用:

my_vec.emplace_back(1, 2);

另请注意,即使这些有效:

my_vec.emplace_back(std::pair<int, int>{1, 2});
my_vec.emplace_back(temp_pair);   

不应该使用它们。与 push_back 相比,它们没有任何优势。 emplace_back 的全部意义在于避免创建临时 T。以上调用都创建临时 std::pair<int, int>.


but I thought you can use emplace_back() anywhere that you have push_back()

大部分是正确的。至少那是意图。你确实可以在你的 cese 中使用它。你只需要稍微调整一下语法。因此,您可以使用 emplace_back(1, 2).

而不是 push_back({1, 2})

不幸的是,在某些情况下您无法使用 emplace_back:聚合。

struct Agg
{
    int a, b;
};

auto test()
{
    Agg a{1, 2}; // ok, aggregate initialization

    std::vector<Agg> v;
    v.emplace_back(1, 2); // doesn't work :(
}

这不起作用,除非您为 Agg 添加构造函数。这被认为是标准中的一个开放缺陷,但不幸的是他们找不到好的解决方案。问题在于 brace init 初始化是如何工作的,如果你在通用代码中使用它,你可能会错过一些构造函数。对于所有细节,请查看这个很棒的 post:

1) {1, 2} 不是表达式

语法

{1, 2}

与 C++ 中的其他东西相比非常“奇怪”。

通常在 C++ 中,您有一个表达式(例如 x + 1.2)并且该表达式具有推导类型...例如,如果 x 是一个 int 变量,则该变量的类型由于隐式转换 intdouble 以及加法的工作原理,表达式将是 double

现在回到 {1, 2}:这很“奇怪”,因为尽管看起来像一个表达式,但它不是......它只是语法,其含义将取决于它的使用位置。

从某种意义上说,在这里输入与大多数 C++ 位置相反:通常在 C++ 中它是“输入”→“输出”(从组件中“出现”的类型)但这里是“输出”→“输入” (类型在组件中被“注入”)。

文本 {1, 2} 本身并不足以编译(根据使用位置不同,它可能有不同的含义)。

所有这一切都归结为这样一个事实,即 {1, 2} 不能像表达式一样 完全 使用,即使规则经过精心设计以欺骗您认为它确实如此.

2) emplace_back 接受构造函数参数

emplace_back 旨在能够直接在容器的最终位置构建对象......预期参数是构造函数的参数,这样做是为了避免创建临时对象只是为了能够为最终目的地制作副本,然后将其丢弃。 因此,emplace_back 的预期参数是 12... 不是一个单一的东西,因为不构建一个临时的单一东西正是 emplace_back 设计的原因。

您可以传递 emplace_back 一个实例,因为包含的类型具有复制构造函数,并且该实例被视为复制(移动)构造函数的参数,而不是要复制(移动)到目标中的对象 ( push_back 期待什么)。本例中执行的操作相同,只是角度不同

后果

总而言之:emplace_back 不能使用 {1, 2} 因为它可以接受任何东西(因此没有提供足够的“上下文”)并且该语法没有足够的意义。 push_back 相反可以接受它,因为它需要一个特定的类型,这为解释语法 {1, 2} 提供了足够的上下文。 这是一个简化的解释,但像往常一样,C++ 进入了一个更加复杂的解析和特殊情况的方向,所以我能理解为什么你不清楚。

然而,关键点是 emplace_back 并不意味着需要一个完整的对象...用于 push_back。当您想要传递用于在容器中构建最终对象的构造函数参数时,应使用新的 emplace_back 构造。