【问题标题】:Efficiently instert tuple into container through move通过移动有效地将元组插入容器
【发布时间】:2016-04-28 05:43:56
【问题描述】:

我是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

【问题讨论】:

  • BTW tuple 以大对象作为映射键不是 IMO 的最佳选择
  • 那你能推荐一个吗? :)
  • 我完全不知道你的问题是什么。
  • 您显然是对的:这是 memoizator 的一部分,其中 args 是输入(为了简单起见,我没有使用作为 memoized 函数的返回类型的值类型)
  • 好的第一个可能的改进是使用unordered_map,因为密钥顺序无关

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


【解决方案1】:

因为这是为了记忆,所以这两个选项都不是一个好主意。

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

但是,您还希望避免不必要地复制密钥,因此插入 value_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 */);
}

【讨论】:

  • 感谢您的回答,非常感谢。第一个问题:我将 const 用于 args,因为这是管理左值引用时的好习惯。那么如果我们删除它会发生什么变化(因为您的评论“那里的密钥是 const 限定的,因此无法从中移动。”)。第二个问题:没有必要保留 map 保证的键顺序,因此 unordered_map 可能是首选。但性能更好的 google::dense_hash_map 会更好,所以问题是:这种结构或任何第 3 方哈希结构都可以进行异构查找吗?
  • @justHelloWorld 1) 问题是mapvalue_type (pair&lt;const Key, value&gt;) 中的常量限定,而不是args;你无能为力。 2) unordered_map 不支持异构查找;我不知道第三方哈希结构 - 你必须检查他们的文档。
  • 我认为您的代码不起作用,因为您正在比较tuple&lt;const Args&amp;...&gt;(所以key)和tuple&lt;const Args ...>`(所以it-&gt;first),但请在@987654321 中回答@我在这个问题上打开的问题。
  • @justHelloWorld std::tuple 可以很好地处理异构比较。你的错误在别处。
  • 请回答/评论其他问题。顺便说一句,正如你所看到的,我几乎复制并粘贴了你的代码,我找到了一个不使用 std::tie 的解决方案,所以我认为问题不在其他地方:D
【解决方案2】:
template <typename... Args>
void foo(const Args & ... args){
    map<tuple<Args...>, int> cache;
    auto result = cache.emplace(move(make_tuple(args ...)),1);
    //...
}

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

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

【讨论】:

  • 谢谢,我很高兴我终于开始了解move 技巧:)
  • BTW 就地建造是因为安放而不是因为移动
  • 但是move(...) 没用?
  • @justHelloWorld 说实话……我不知道。我不确定完美的转发是否会采用 maketuple 并就地执行它。如果是这样,那么移动是无用的,否则它很重要。您可能不会接受答案,因为知识渊博的人会给您更好的答案
  • 感谢您的诚实 :)
【解决方案3】:

第一:

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&lt;Args&gt;(args)... 将其作为右值引用转发,从而导致移动而不是复制。如果您使用左值引用调用foo(),它将作为左值转发。

【讨论】:

  • 至少,放弃const
  • @T.C.是复制/粘贴疏忽,谢谢。但是“至少”是什么意思?
猜你喜欢
  • 2015-10-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-30
  • 1970-01-01
  • 2017-07-20
  • 2011-09-07
  • 2016-10-31
相关资源
最近更新 更多