【问题标题】:Is inserting in the end equivalent to std::copy()?最后插入是否等同于 std::copy()?
【发布时间】:2013-01-01 04:16:11
【问题描述】:

考虑以下两种将元素附加到向量中的方法

std::vector<int> vi1(10,42), vi2;

vi2.insert(vi2.end(),vi1.begin(),vi1.end());
<OR>
std::copy(vi1.begin(),vi1.end(),std::back_inserter(vi2));

std::copy 版本看起来更干净,我不必输入两次vi2。但是由于它是一个通用算法,而 insert 是一个成员函数,insert 是否可以比std::copy 执行得更好,还是它做同样的事情?

我可以对自己进行基准测试,但我必须针对每种模板类型的每个向量进行基准测试。有人做过吗?

【问题讨论】:

    标签: c++ stl-algorithm


    【解决方案1】:

    它不等同于std::copy。相当于push_back(某种意义上)。

    是的,std::back_inserter 做同样的事情,你可以使用 std::copy,如果你使用 std:front_inserter,它也可以插入到前面(尽管你不能将它与 std::vector 一起使用)。如果您改用std::inserter,它也可以在指定的迭代器处插入。所以你看,std::copy 根据你作为第三个参数传递的内容来做事。

    现在回到问题的本质。我认为你应该使用insert,因为它可以更好地执行,因为它可能会发现要插入的元素的数量,因此它可能会一次分配那么多内存(如果需要)。在您的情况下,它的性能可能会更好,因为 v1std::vector,这意味着在 O(1) 时间内计算元素的数量很容易。

    【讨论】:

    • 您不能将std::front_inserterstd::vector 一起使用。
    • @JamesKanze:是的。谢谢。已编辑。
    【解决方案2】:

    vector::insert 在大多数情况下可能会在 C++ 标准库的大多数主流实现上表现得更好。原因是vector 对象具有当前分配的内存缓冲区的内部知识,并且可以预先分配足够的内存来执行整个插入,因为可以使用随机访问迭代器预先计算元素的数量。但是,std::copystd::back_inserter 将继续调用 vector::push_back,这可能会触发多次分配。

    例如,如果迭代器类别为RandomAccessIterator,则libstdc++ 中std::vector::insert 的GNU 实现会提前预分配一个缓冲区。使用输入迭代器,vector::insert 可能等价于std::copy,因为您无法提前确定元素的数量。

    【讨论】:

      【解决方案3】:

      有一些细微的差别。在第一种情况下(std::vector&lt;&gt;::insert),您为容器提供了一个范围,因此它可以计算距离并执行单个分配器以增长到最终所需的大小。在第二种情况下(std::copy),信息不直接存在于接口中,它可能会导致缓冲区的多次重新分配。

      请注意,即使需要多次重新分配,插入的摊销成本仍必须保持不变,因此这并不意味着成本渐近变化,但可能很重要。另请注意,库的一个特别智能的实现具有所有必需的信息,通过专门处理 std::copy 的行为来专门处理反向插入迭代器(尽管我没有检查任何实现是否真的这样做),从而使第二个版本变得高效。

      【讨论】:

      • 你知道任何可以通用实现你最后一个建议的特殊技巧吗? (我唯一能想到的就是让std::back_iterator 派生自一个标记类,然后发送它。)
      • @JamesKanze:你可以有一个traits类型告诉back_insert_iterator它的容器类型是否通告了一个insert函数,这相当于重复调用push_back。然后back_insert_iterator 上的一个函数转发到该函数,并有另一个特征类型告诉copy 它的OutputIterator 类型接受该调用。如果您以__ 开头的函数调用它们,则可以自动检测函数的存在:vector::__insert_at_endback_insert_iterator::__delegate_copy。我认为与您所说的结果相同。
      • 无论如何,我的意思是back_insert_iterator 必须以某种方式知道它的容器允许insert 代替push_back。对于用户定义的类型,您不能随便进行更改,您需要容器类型的显式权限才能执行与标准规定的push_back调用不同的操作。
      【解决方案4】:

      您会认为vector::insert 可能能够优化它一次插入多个项目的情况,但它比看起来更难。例如,如果迭代器是输出迭代器怎么办 - 无法提前知道您将执行多少次插入。 insert 的代码很可能只是执行多个 push_backs 与 back_inserter 相同。

      【讨论】:

      • 如果要插入的迭代器是随机访问迭代器,标准只允许单次重新分配,因此插入的实现不能只是push_back的序列。
      • @JamesKanze,我不知道 - 如果你能引用标准并将其放入答案中,你会得到我的支持。
      • 它隐含在insert 的复杂性要求中。 C++03 对除输入迭代器外的所有迭代器都要求线性复杂度;适用于所有迭代器的 C++11!据推测,C++03 中的想法是首先使用std::distance 来确定分配的大小。我不知道这应该如何与 C++11 中的输入迭代器一起工作。
      • @JamesKanze,摊销线性是否足以满足规范?我认为多个push_backs 符合条件,因为缓冲区分配应该是摊销常数。
      • 我真的不知道。我确实知道,对于来自迭代器的构造函数,复杂性要求更详细,并根据迭代器的类型限制重新分配的数量。当然,迭代器构造函数最明显的实现就是构造一个空向量,然后调用insert
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-09-08
      • 1970-01-01
      • 2020-07-18
      • 2010-12-15
      • 2014-07-02
      • 1970-01-01
      • 2021-12-12
      相关资源
      最近更新 更多