将元素移出关联容器

时间:2013-01-20 20:50:28

标签: c++ sorting c++11 move-semantics multiset

为了好玩,我实现了可以想象的最简单的排序算法:

template<typename Iterator>
void treesort(Iterator begin, Iterator end)
{
    typedef typename std::iterator_traits<Iterator>::value_type element_type;

    // copy data into the tree
    std::multiset<element_type> tree(begin, end);

    // copy data out of the tree
    std::copy(tree.begin(), tree.end(), begin);
}

对于我的测试数据,它只比std::sort慢大约20倍:)

接下来,我想用移动语义来提高性能:

template<typename Iterator>
void treesort(Iterator begin, Iterator end)
{
    typedef typename std::iterator_traits<Iterator>::value_type element_type;

    // move data into the tree
    std::multiset<element_type> tree(std::make_move_iterator(begin),
                                     std::make_move_iterator(end));
    // move data out of the tree
    std::move(tree.begin(), tree.end(), begin);
}

但即使我正在对std::string进行排序,这并没有显着影响性能。

然后我记得关联容器在外面是不变的,也就是说,std::movestd::copy会在这里做同样的事情:(有没有其他方法可以将数据移出树外?

3 个答案:

答案 0 :(得分:8)

std::setstd::multiset仅提供 const 对其元素的访问权限。这意味着你不能移动一些东西。如果您可以移出项目(或根本不修改它们),您可以通过更改项目的排序顺序来中断该项目。所以C ++ 11禁止它。

因此,您尝试使用std::move算法只会调用复制构造函数。

答案 1 :(得分:4)

我相信你可以为multiset使用自定义分配器(第3个模板参数),它实际上将其destroy方法中的元素移回用户的容器。然后擦除集合中的每个元素,在销毁过程中,它应该将字符串移回原始容器。我认为自定义分配器需要具有2阶段构造(将传递给你的treesort函数的开始迭代器传递给成员,但不能在构造期间,因为它必须是默认构造的。)

显然,这将是奇怪的,并且在set / multiset中没有pop方法是一个愚蠢的解决方法。但它应该是可能的。

答案 2 :(得分:0)

我喜欢Dave关于一个奇怪的分配器的想法,它记住每个移动构造对象的来源并自动移回破坏,我从来没有想过这样做!

但这是一个更接近原始尝试的答案:

template<typename Iterator>
void treesort_mv(Iterator begin, Iterator end)
{
    typedef typename std::iterator_traits<Iterator>::value_type element_type;

    // move the elements to tmp storage
    std::vector<element_type> tmp(std::make_move_iterator(begin),
                                  std::make_move_iterator(end));
    // fill the tree with sorted references
    typedef std::reference_wrapper<element_type> element_ref;
    std::multiset<element_ref, std::less<element_type>> tree(tmp.begin(), tmp.end());

    // move data out of the vector, in sorted order
    std::move(tree.begin(), tree.end(), begin);
}

这会对multiset个引用进行排序,因此不需要将它们移出树。

但是,当移回原始范围时,移动分配对于自我分配不一定是安全的,所以我先将它们移动到矢量中,这样当它们重新分配回原始范围时就不会有自我分配-assignments。

在我的测试中,这略微比原始版本快。它可能会失去效率,因为它必须分配向量以及所有树节点。那个以及我的编译器使用COW字符串如此移动的事实并不比复制快得多。