【问题标题】:Moving elements out of an associative container将元素移出关联容器
【发布时间】:2013-01-03 22:46:00
【问题描述】:

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

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::strings。

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

【问题讨论】:

  • 你能给我们一些关于你正在排序的字符串的更多信息吗?您能否发布您的测试代码供我们使用?
  • 看起来您正在尝试优化一些您知道可以通过简单地使用qsort 来进行更多优化的东西。这样做的目的是什么?学习移动语义?
  • @svick 是的,我的主要问题是:如果我不再需要元素,如何将元素移出关联容器?我确信一般问题超越了我愚蠢的 Treesort 示例 :) 我修改了问题的标题并删除了分配器位,谢谢。
  • 我怀疑答案是你不能——似乎没有弹出和返回。使该异常安全可能与在std::vector 上一样棘手,甚至更多。
  • 你不能,但是 Howard Hinnant 做了一个扩展(我认为没有官方文件,但我可能错了),它可以让你从容器中提取一个节点,然后你可以移动它的值。所以也许在几年后,使用更新的标准......

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


【解决方案1】:

std::setstd::multiset 仅提供 const 对其元素的访问。这意味着您不能将某些东西移出集合。如果您可以将项目移出(或完全修改它们),则可以通过更改项目的排序顺序来破坏集合。所以 C++11 禁止它。

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

【讨论】:

    【解决方案2】:

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

    显然这会很奇怪,而且是一个愚蠢的解决方法,因为在 set/multiset 中没有 pop 方法。不过应该是可以的。

    【讨论】:

      【解决方案3】:

      我喜欢 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 进行排序,因此无需将它们移出树。

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

      这比我的测试中的原始版本快。它可能会降低效率,因为它必须分配向量以及所有树节点。这一点以及我的编译器使用 COW 字符串这一事实,所以移动并不比复制快多少。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-06-06
        • 2013-11-02
        • 1970-01-01
        • 2013-05-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多