为了好玩,我实现了可以想象的最简单的排序算法:
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::move
和std::copy
会在这里做同样的事情:(有没有其他方法可以将数据移出树外?
答案 0 :(得分:8)
std::set
和std::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字符串如此移动的事实并不比复制快得多。