如何使用std::vector::emplace_back和vector<vector<int>>?

25
  vector<vector<int> > res;
  res.emplace_back({1,2}); // change to res.push_back({1,2}); would work

这给了我一个错误

main.cpp:61:25: error: no matching function for call to ‘std::vector<std::vector<int> >::emplace_back(<brace-enclosed initializer list>)’
main.cpp:61:25: note: candidate is:
In file included from /usr/include/c++/4.7/vector:70:0,
                 from /usr/include/c++/4.7/bits/random.h:34,
                 from /usr/include/c++/4.7/random:50,
                 from /usr/include/c++/4.7/bits/stl_algo.h:67,
                 from /usr/include/c++/4.7/algorithm:63,
                 from miscalgoc.hpp:1,
                 from main.cpp:1:
/usr/include/c++/4.7/bits/vector.tcc:92:7: note: void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {}; _Tp = std::vector<int>; _Alloc = std::allocator<std::vector<int> >]

如何让这个工作?另外,为什么需要一个分配器(allocator)?


你在使用 push_back 而不是 emplace_back 时遇到了什么实际问题?你使用的编译器和标准库的名称和版本是什么? - Yakk - Adam Nevraumont
5个回答

46
问题在于函数模板参数无法从大括号初始化列表(例如{1,2})中推导出std::initializer_list。

示例:

#include <initializer_list>
#include <type_traits>


template<typename T>
void func(T arg) {
}


int main() {
    auto init_list = {1, 2};    // This works because of a special rule
    static_assert(std::is_same<decltype(init_list), std::initializer_list<int>>::value, "not same");

    func(std::initializer_list<int>{1, 2});     // Ok. Has explicit type.

    func({1, 2});   // This doesn't because there's no rule for function
                    //      template argument to deduce std::initializer_list
                    //      in this form.
}

实时示例

std::vector::emplace_back() 是一个函数模板,其参数是被推导的。因此,传递{1, 2}将不起作用,因为它无法推断。需要给它加上明确的类型。

res.emplace_back(std::initializer_list<int>{1,2});

需要让它工作。

实时示例


6

@Mark的答案相当正确。现在让我们考虑一个更实际的情况。经过一些适当的操作,您收集了一些带有vector<int>的数据,并且想将其推入vector<vector<int>>中:

std::vector<std::vector<int>> res;

for (int i = 0; i < 10000; ++i) {
    //
    // do something
    //
    std::vector<int> v(10000, 0);  // data acquired
    res.push_back(v);
}

这并不像是给你已经知道的值赋值。使用 std::initializer_list 可能不再是一个解决方案。在这种情况下,你可以使用 std::move(连同 emplace_backpush_back 中的任何一个都可接受)。

for (int i = 0; i < 10000; ++i) {
    std::vector<int> v(10000, 0);  // will become empty afterward
    res.emplace_back(std::move(v));  // can be replaced by 
                                     // res.push_back(std::move(v));
}

性能或多或少得到了提升。您仍然可以从xvalue移动插入的概念中受益,通过移动构造函数构建对象而不是复制对象。 更新 res.push_back(move(v))之所以有效是因为在C++11之后,它们重载了方法std::vector::push_back(value_type&& val),特意支持右值引用。原因就在于此。

4
请查看vector::emplace_back文档。通过使用传递的参数调用新元素的构造函数,emplace_back试图在向量中创建一个新元素。因此,当您调用emplace_back({1,2})时,它试图将{1,2}传递给构造函数,但由于res是一个int向量的向量,它正在查找向量构造函数,其中没有一个可以接受括号括起来的初始化列表。
同样,请查看vector::push_back文档。当调用push_back时,它会创建默认对象(在本例中为int向量)并将值复制到其中。我猜想push_back({1,2})之所以有效,可能是因为大括号括起来的初始化列表会创建一个值类型,而push_back接受该值类型。

push_back没有这个问题。 - user40129

1

虽然这个问题目前已经得到了很好的回答,但我想详细阐述为什么在这种情况下 push_back 起作用。

this cppreference page 我们可以看到

当满足以下条件时,std::initializer_list 对象会自动构造:

  1. 使用花括号初始化列表来初始化一个对象,该对象的对应构造函数接受 std::initializer_list 参数,
  2. 使用花括号初始化列表作为赋值运算符的右操作数或函数调用的参数,并且对应的赋值运算符/函数接受 std::initializer_list 参数,
  3. 将花括号初始化列表绑定到 auto 类型,包括在范围 for 循环中。
  1. 不是我们的情况。

push_back 本身不接受 initializer_list,但它接受 T。在我们的情况下,Tstd::vector<int>。所以第 2 点也不适用。

此列表中提到了一种初始化方式:list initialize,并列举了其他方式。

function ({ arg1, arg2, ... })

因此,把所有内容放在一起,你可以调用接受std::vector<int>braced-init-listpush_back函数。使用braced-init-liststd::vector进行list-initialized,从而构造向push_back(或者说是它的引用)传递的向量。

emplace_back不能与braced-init-list一起使用,因为它不接受类型为T(在我们的例子中为std::vector<int>)的参数。

你可以尝试以下代码:

#include <iostream>
#include <initializer_list>

struct V {
    int i;
    int j;

    V(int i, int j) {
        std::cout << "i j constructor\n";
    }

    V(std::initializer_list<int> il) {
        std::cout << "init list constructor\n";
    }
};

void
test_f(const V &v) {
    std::cout << "test_f called\n";
}

int main(void) {
    test_f( {1, 2, 3} );
    return 0;
}

输出将是:

init list constructor
test_f called

-1

vector<vector > res;

res.emplace_back({1,2});

默认类型检测和转换只会发生一次。它在使用情况中不会重复工作。要使其工作,您需要两个推断。

  1. {1,2} -> 推断容器是整数的初始化列表。

  2. res.emplace_back( any_initializer_list_of_ints ) -> 这里由于成员元素类型已知为整数向量,并且可以使用整数的初始化列表来构造整数向量,因此需要将初始化列表转换为向量。

编译器不会在同一位置进行两次推断和转换。所以这永远不会起作用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接