std::vector::insert 与 std::list::operator[]

Posted

技术标签:

【中文标题】std::vector::insert 与 std::list::operator[]【英文标题】:std::vector::insert vs std::list::operator[] 【发布时间】:2016-08-05 15:32:05 【问题描述】:

我知道std::list::operator[] 没有实现,因为它的性能很差。但是std::vector::insertstd::list::operator[] 一样低效。背后的解释是什么?

【问题讨论】:

“效率低下”确实取决于,但这不是可以假设的。 “效率低下” - 高度投机 并且完全取决于使用场景。您的问题将很好地证明情况确实如此。 对于std::list::operator[],如果你真的想要,你可以自己做,对于std::vector::insert,你不能。 这两种方法都可以任意使用。因此,对于某些特定情况,我认为没有必要讨论它们。但是对于其中之一,实施被认为是不合适的。为什么? @WhozCraig:你的断言非常没有帮助。你必须知道他们错了。你不可能不知道他们是秃头。无论如何,对于其他读者:vector::insert 和假设的list::operator[] 通常在容器的大小 n 上具有 O(n) 复杂度。然而,索引运算符可以针对顺序访问进行优化。向量对原始数组的直接索引的有效要求使得顺序插入优化可能是不可能的。 【参考方案1】:

std::vector::insert 被实现是因为std::vector 必须满足SequenceContainer concept 的要求,而operator[] 不是任何概念所必需的(据我所知),可能会在c 中的ContiguousContainer 概念中添加++17。所以operator[]添加到可以像数组一样使用的容器中,而insert是接口规范要求的,所以满足一定概念的容器可以用在泛型算法中。

【讨论】:

【参考方案2】:

我认为 Slava 的回答做得最好,但我想提供一个补充解释。通常对于数据结构,访问次数比插入次数多得多,反之亦然。有许多数据结构可能会尝试以插入为代价来优化访问,但相反的情况要少得多,因为它在现实生活中往往不太有用。

operator[],如果为链表实现,大概会访问一个现有的值(类似于它对vector 所做的事情)。 insert 添加新值。如果后续访问速度非常快,您更有可能愿意在vector 中插入一些新元素,从而降低性能。在许多情况下,元素插入可能完全在关键路径之外,而关键路径由单个遍历或已经存在的数据的随机访问组成。因此,在这种情况下让insert 为您处理细节非常方便(高效且正确地编写实际上有点烦人)。这实际上是一种不常见的矢量用法。

另一方面,在链表上使用operator[] 几乎总是表明您使用了错误的数据结构。

【讨论】:

"operator[] 总是访问一个现有的值。"不适用于std::map @Bathsheba 我已经更仔细地限定了我对 operator[] 的陈述,谢谢。不过,我认为这并没有真正改变我回答的实质。 是的,现在这是一个很好的答案。紫外线。【参考方案3】:

std::list::operator[] 需要 O(N) 次遍历,并且与 list 的设计目的不符。如果您需要operator[],请使用不同的容器类型。当 C++ 人看到[] 时,他们假设一个 O(1)(或者,更糟糕的是,一个 O(Log N))操作。为list 提供[] 会破坏这一点。

但是虽然std::vector::insert也是 O(N),它可以被优化:通过vector'可以很容易地优化末端插入s 容量以大块的形式增长。在中间插入需要逐个元素移动,但在现代芯片组上也可以非常快速地执行。

【讨论】:

如果你想在最后插入,那就用push_back,你不需要insert,还是我漏了你的意思? 确实可以使用push_back;并且适用于末端插入的类似优化。 如果我们想在一行中插入多个元素,我们会这样做。 我发现关于优化的评论具有误导性,因为它是列表的索引运算符,可以很容易地优化,而向量插入可能不能,而且由于大块的增长是向量的作用. 我不太明白这个答案。中间的插入总是 O(N),就像list::operator[]。最后插入更快,但插入的接口比那更通用,所以这无关紧要。如果它可以“在许多平台上快速执行”,那么您基本上是在说尽管两者都是 O(N),但在向量中间的插入具有更好的常数,因此如果您认为这是答案,则应该强调这一点。 【参考方案4】:

[] 运算符继承自普通数组。它一直被理解为底层容器的快速(亚线性时间)访问器。由于 list is 不支持亚线性时间访问,所以它实现算子没有意义。

auto a = Container [10]; // Ideally I can assume this is quick

您的std::list <>::operator [] 等效于std::next <std::list<>::iterator>。记录在cpp-reference。

auto a = *std::next (Container.begin (), 10); // This may take a little while

这是索引容器的真正通用方式。如果容器支持随机访问,则为常数时间,否则为线性。

【讨论】:

Re“由于列表不支持恒定时间随机访问”,否,例如std::map 提供具有对数复杂度的索引运算符,因此该论点不成立。完全没有。 另外,草拟的等价(以及它等价的断言)是不好的。如果std::list 提供索引,它很可能会针对顺序访问进行优化。 −1 "如果容器支持随机访问,它将是常数时间,否则它将是线性的。"是完全错误的™。 我不确定我是否同意 DV。 C++ 标准库应该从不允许 O(N) 的 [],尽管允许 O(Log N)。紫外线。 @Bathsheba:我知道 lg 是什么意思。你知道什么比 lg n 更快?日志* n。所以也许标准库应该只允许这样做。我的观点是你的截止是完全任意的。

以上是关于std::vector::insert 与 std::list::operator[]的主要内容,如果未能解决你的问题,请参考以下文章

重叠向量::插入的行为

std::vector 与 std::list 与 std::slist 的相对性能?

std::bind 与 std::function 与 Clang 崩溃

std::dynarray 与 std::vector

为啥 pandas.Series.std() 与 numpy.std() 不同?

std::vector::resize() 与 std::vector::reserve()