【问题标题】:Should STL map::insert support move semantics with move_iterators?STL map::insert 是否应该支持 move_iterators 的移动语义?
【发布时间】:2016-11-02 11:22:36
【问题描述】:

我有大量数据要从文件加载到 map 中,该文件已经按排序顺序(因为它是从地图中序列化的)。 我发现首先将数据加载到vector,然后使用insert 批量加载到map 会更快。这在 19 秒的加载时间内节省了一秒多一点。

value_type 是一个包含其他结构向量的结构。加载完成后我不需要vector,因此我在调用map::insert 时使用move_iterators。使用 MSVC 2015 (VC14),数据不会移动到地图中,而是通过 STL 代码深处的const_reference 复制。

这是一个符合标准的实现,忽略数据的移动吗?

template<typename Stream, typename Key, typename Type, typename Traits, typename Allocator>
bool const read(Stream &is, std::map<Key, Type, Traits, Allocator> &map)
{
    size_t size;
    read(is, size);

    std::vector<std::pair<Key, Type>> items(size);
    for (size_t i=0; i<size; ++i)
    {
        auto &item = items[i];
        if (!read(is, item.first)  ||  !read(is, item.second))
            return false;
    }
    map.insert(std::make_move_iterator(items.begin()), std::make_move_iterator(items.end()));

    return !!is;
}

我已经解决了用

替换map.insert的问题
for (auto &item : items)
    map.insert(std::move(item));

但它不是那么整洁,但确实节省了 0.6 秒。

【问题讨论】:

    标签: c++11 dictionary stl move move-semantics


    【解决方案1】:

    这是一个符合标准的实现,忽略数据的移动吗?

    标准对 insert() 功能的规定:

    要求: value_type 应为 EmplaceConstructible*iX

    所以必须可以从迭代器的reference 类型构造value_type,在这种情况下它是一个右值引用。这意味着您可以使用仅移动(即不可复制)键类型和映射类型,并且只要迭代器返回可以转换为映射的value_type 的右值,它就必须工作。

    应该使用构造函数std::pair&lt;const Key, Type&gt;::pair&lt;K2, T2&gt;(pair&lt;K2, T2&gt;&amp;&amp;) 将值从来自迭代器的右值构造到映射中。

    这适用于我的 GCC,以及使用在线编译器的最新 VC++:

    #include <vector>
    #include <map>
    #include <iterator>
    
    struct MoveOnly {
      MoveOnly() = default;
      MoveOnly(MoveOnly&&) = default;
    };
    
    bool operator<(const MoveOnly&, const MoveOnly&) { return false; }
    
    template<typename Key, typename Type, typename Traits, typename Allocator>
    void read(std::map<Key, Type, Traits, Allocator> &map)
    {
        std::vector<std::pair<Key, Type>> items(1);
        map.insert(std::make_move_iterator(items.begin()), std::make_move_iterator(items.end()));
    }
    
    int main()
    {
      std::map<MoveOnly, MoveOnly> m;
      read(m);
    }
    

    【讨论】:

      【解决方案2】:

      如果您已指定自己的移动构造函数或移动赋值运算符,则应将其标记为“noexcept”。否则标准容器将复制元素。 在此处查看文档:http://en.cppreference.com/w/cpp/language/noexcept_spec

      【讨论】:

      • std::vector 是这样,但对于插入地图来说并不重要。
      • 这不是真的。我相信他们提到 std::vector 只是作为一个例子:) 这是另一个链接:en.cppreference.com/w/cpp/language/move_constructor。注释部分说:为了使强异常保证成为可能,用户定义的移动构造函数不应抛出异常。事实上,当容器元素需要重新定位时,标准容器通常依靠 std::move_if_noexcept 在移动和复制之间进行选择。
      • @klimov: 小心,你是在和以维护 gcc std::lib 为生的人争论! :-)
      • @klimov, map 在其移动赋值运算符中使用move_if_noexcept,但通常不会在从一系列迭代器插入时(当它不重新定位任何元素时)。您可以修改我的答案中的示例以具有抛出移动构造函数和复制构造函数,并看到它仍然移动。或者在第 23 条中搜索提及 is_nothrow_move_constructible 的标准,并将 [vector.modifiers] 与 [map.modifiers] 进行比较。您的建议总体上是好的,但与这个特定问题无关。
      猜你喜欢
      • 2012-11-16
      • 1970-01-01
      • 2016-05-06
      • 2016-12-19
      • 1970-01-01
      • 1970-01-01
      • 2017-07-02
      • 2011-02-12
      • 2022-01-14
      相关资源
      最近更新 更多