Is inserting in the end equivalent to std::copy()?
考虑以下两种方法将元素附加到向量中
1 2 3 4 5
| 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更好,或者它做的是相同的事情?
我可以自己做基准测试,但是我必须为每种模板类型的每一个向量做基准测试。有人做过吗?
有一些细微的差别。在第一种情况下(std::vector<>::insert中),您为容器提供了一个范围,这样它就可以计算距离并执行单个分配器以增长到最终所需的大小。在第二种情况下(std::copy中),信息不直接出现在接口中,可能导致缓冲区的多次重新分配。
请注意,即使需要多次重新分配,插入的摊余成本也必须保持不变,因此这并不意味着渐进的成本变化,但可能很重要。还要注意的是,库的一个特别智能的实现具有所有必需的信息,通过专门化std::copy的行为来专门处理back-insert迭代器,从而使第二个版本具有同样的效率(尽管我没有检查是否有任何实现真正做到这一点)。
- 你知道有什么特殊的技巧可以让你的最后一个建议得到一般的实现吗?(我现在唯一能想到的就是让std::back_iterator从一个标记类派生,并分发这个类。)
- @jameskanze:你可以有一个traits类型来告诉back_insert_iterator它的容器类型是否宣传一个insert函数,这个函数相当于对push_back的重复调用。然后,back_insert_iterator上的一个函数转发到该函数,并有另一个traits类型来告诉copy它的OutputIterator类型接受该调用。如果从__:vector::__insert_at_end和back_insert_iterator::__delegate_copy开始调用函数,则可以自动检测函数的存在。我想结果和你说的一样。
- 无论如何,我的观点是,back_insert_iterator必须知道它的容器允许insert代替push_back。对于用户定义的类型,您不能只是随意地进行更改,您需要容器类型的显式权限来执行与标准强制执行的push_back调用不同的操作。
您可能认为vector::insert可以优化一次插入多个项目的情况,但这比看起来要困难。例如,如果迭代器是输出迭代器,那么会怎样呢?没有办法提前知道要执行多少插入操作。很可能insert的代码只是多个push_back与back_inserter相同。
- 如果要插入的迭代器是rendom访问迭代器,则该标准只允许单个重新分配,因此insert的实现不能只是push_back的序列。
- @Jameskanze,我不知道-如果你能引用这个标准并把它写进一个答案,你会得到我的赞成票。
- 它隐含在insert的复杂性需求中。C++ 03对所有的输入迭代器都需要线性复杂度;对于所有迭代器,C++都是11!大概,C++ 03中的想法是首先使用EDCOX1,18来确定分配的大小。我不知道如何在C++ 11中使用输入迭代器。
- @Jameskanze,摊余线性是否足以满足规范要求?我认为多个push_back是符合条件的,因为缓冲区分配应该是摊销常数。
- 我真的不知道。我知道对于迭代器的构造函数,复杂性要求更详细,并且根据迭代器的类型限制重新分配的数量。当然,迭代器构造函数最明显的实现是构造一个空向量,然后调用insert。
- @markransom:具体来说,在初始大小为n的向量上,k调用push_back是o(n+k)。这与执行单个重新分配后再执行k份拷贝/移动的复杂性相同。在根目录,如果对push_back的调用是"摊余固定时间",则表示对push_back的调用是"线性时间"。你不必担心"摊余线性时间"。最后,我认为insert和迭代器构造函数都应该在迭代器标记上调度,但是标准只能规定通过限制重新分配的数量,时间复杂性不能达到目的。
- 德拉特,我应该用m而不是k。为了清楚起见,我不把k当作常量。
在大多数情况下,EDCOX1〔11〕可能在C++标准库的主流实现中表现更好。原因是vector对象对当前分配的内存缓冲区有内部知识,并且可以预先分配足够的内存来执行整个插入,因为可以使用随机访问迭代器提前计算元素的数量。但是,std::copy和std::back_inserter将继续调用vector::push_back,这可能会触发多次分配。
例如,在libstdc++中,std::vector::insert的GNU实现在迭代器类别为RandomAccessIterator的情况下预先分配缓冲区。对于输入迭代器,vector::insert可能等同于std::copy,因为您不能预先确定元素的数量。
它不等同于std::copy。它相当于push_back(在某种意义上)。
是的,std::back_inserter做了同样的事情,你用std::copy也可以在前面插入,如果你用std:front_inserter(虽然你不能用std::vector)。如果使用std::inserter,它也可以在指定的迭代器中插入。所以你看,std::copy是根据你作为第三个论点所传递的东西来做的。
现在回到问题的本质。我认为您应该使用insert,因为它可以更好地执行操作,因为它可能会发现要插入的元素的数量,所以它可以一次分配那么多的内存(如果需要的话)。在您的例子中,它可能表现得更好,因为v1是std::vector,这意味着在o(1)时间内计算元素的数量很容易。
- 不能将std::front_inserter与std::vector一起使用。
- @是的。谢谢。编辑。