通过移动有效地将元组引入容器中

时间:2016-04-28 05:43:56

标签: c++ c++11 move-semantics emplace

我是一个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_pairmake_tuple

3 个答案:

答案 0 :(得分:3)

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

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

但是,您还希望避免不必要地复制密钥,因此插入value_type并不好 - Key中的emplace是const限定的,因此无法移动。

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

因此,我们需要先查找密钥,如果密钥不在地图中,则只需调用args...。在C ++ 11中,只允许同类查找,因此您必须复制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 */); }

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 */);
}

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

list

答案 1 :(得分:0)

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

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

一般情况下,尽可能使用安全套。它应该永远是一个更好的选择。

答案 2 :(得分:0)

首先:

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

VS

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(),则会将其转发为左值。

相关问题