2

我是move语义初学者。这段代码是:

template <typename... Args>
void foo(const Args & ... args){
    map<tuple<Args...>, int> cache;
    auto result = cache.emplace(move(make_tuple(args ...)),1);
    //...
    }

比以下更有效:

template <typename... Args>
void foo(const Args & ... args){
    map<tuple<Args...>, int> cache;
    tuple<Args...> t(args...);
    auto result = cache.insert(make_pair(t,1));
    //...
    }

特别是如果args包含一些大物体?

同样的问题,但有std::vector(所以不需要make_pairor make_tuple

4

3 回答 3

3

由于这是用于记忆,因此这两个选项都不是一个好主意。

对于unique-key容器,emplaceand insert(除了insert传入a-value_type即,pair<const Key, Value>)可以无条件分配内存并先构造key-value对,如果key已经存在则销毁pair并释放内存;如果您的密钥已经存在,这显然是昂贵的。(他们需要这样做,因为在一般情况下,您必须先构造密钥,然后才能检查它是否存在,并且必须直接在其最终位置构造密钥。)

但是,您还希望避免不必要地复制密钥,因此插入 avalue_type是不好的 -Key那里的 const 限定,因此不能从中移动。

最后,您还希望避免额外的查找。不像内存分配那样昂贵,但仍然可以保存它。

因此,我们需要先查找键,并且仅emplace当键不在地图中时才调用。在 C++11 中,只允许同类查找,因此您必须制作一份args....

map<tuple<Args...>, int> cache;
auto key = std::make_tuple(args...);
auto it = cache.lower_bound(key); // *it is the first element whose key is
                                  // not less than 'key'
if(it != cache.end() && it->first == key) {
    // key already in the map, do what you have to do
}
else {
    // add new entry, using 'it' as hint and moving 'key' into container.
    cache.emplace_hint(it, std::move(key), /* value */);
}

在 C++14 中,您可以进行异构查找,这意味着您可以在实际需要时保存副本:

map<tuple<Args...>, int, less<>> cache; // "diamond functor" enables hetergeneous lookup
auto key = std::tie(args...); // this is a tuple<const Args&...> - a tuple of references!
auto it = cache.lower_bound(key); // *it is the first element whose key is
                                  // not less than 'key'
if(it != cache.end() && it->first == key) {
    // key already in the map, do what you have to do
}
else {
    // add new entry, copying args...
    cache.emplace_hint(it, key, /* value */);
}
于 2016-05-01T02:05:33.873 回答
0
template <typename... Args>
void foo(const Args & ... args){
    map<tuple<Args...>, int> cache;
    auto result = cache.emplace(move(make_tuple(args ...)),1);
    //...
}

这段代码应该更快。emplace执行就地构建(完美转发)。这应该保证最小数量的构造和副本。但是,如果您对它们进行基准测试,它不会有害。

一般来说,尽可能使用 emplace。它应该总是一个更好的选择。

于 2016-04-28T05:49:03.187 回答
0

第一的:

auto result = cache.emplace(move(make_tuple(args ...)),1);

对比

auto result = cache.emplace(make_tuple(args ...),1);

没有什么区别。make_tuple(args...)是一个临时的,因此作为右值引用传递。此举不会添加任何内容。

会有所不同

tuple<Args...> t(args...);
auto result = cache.emplace(t, 1);

现在emplace()接收一个左值引用,因此使用 std::pair 的复制构造函数而不是移动构造函数。

无论如何,如果大数据存在于任何问题中,那么args...无论如何您的问题都存在于其他地方。所有args当前都作为左值引用传递。

你想做的是:

template <typename... Args>
void foo(Args && ... args){
    map<tuple<Args...>, int> cache;
    auto result = cache.emplace(make_tuple(forward<Args>(args)...)),1);
    //...
}

如果您将右值引用传递给foo(),则将forward<Args>(args)...其作为右值引用转发,从而导致移动而不是复制。如果您foo()使用左值引用调用,它将作为左值转发。

于 2016-04-30T23:50:47.040 回答