为什么 vector::迭代器在重新分配时失效

Why is vector::iterator invalidated upon reallocation?

本文关键字:分配 失效 新分配 vector 迭代器 为什么      更新时间:2023-10-16

我不明白为什么当重新分配发生时,vector的迭代器应该失效。

难道不能通过在迭代器中存储偏移量而不是指针来防止这种情况吗?

为什么vector不是这样设计的?

只是为了引用与性能相关的理由:在设计C++时,Stroustrup 认为像 std::vector 这样的模板类接近本机数组的性能特征至关重要:

强调运行时效率的一个原因...是我想要的 模板在时间和空间上足够高效,可用于 低级类型,如数组和列表。

更高级别的替代方案 - 例如,一个带有 size() 的范围检查数组 操作,多维数组,具有正确数字的向量类型 向量运算和复制语义等 -- 将被接受 用户仅当他们的运行时、空间和符号方便时 接近内置数组的那些。

换句话说,提供参数化类型的语言机制 应该使相关用户能够负担得起 消除数组的使用,转而使用标准库类。

Bjarne Stroustrup,C++的设计与演变,第342页。

因为要让迭代器做到这一点,它们需要存储指向矢量对象的指针。 对于每个数据访问,它们需要跟随指向向量的指针,然后跟随其中的指针指向数据数组的当前位置,然后将偏移量 * 元素大小相加。 这会慢得多,并且需要为size_type成员提供更多内存。

当然,有时这是一个很好的折衷方案,能够在需要时选择它会很好,但它比(C 样式)直接阵列使用更慢、更笨重。 在引入 STL 时,std::vector 的性能受到了无情的审查,并且正常实现针对空间和速度进行了优化,超过了这个便利性/可靠性因素,就像阵列等效operator[]与阵列一样快,但不如at()安全。

您可以通过包装标准std::vector<T>::iterator来增加安全性,但不能通过包装extension::vector<T>::safe_iterator来增加速度。这是一个一般原则,并解释了许多C++设计选择。

这些决定有很多原因。正如其他人指出的那样,向量iterator的最基本实现是指向元素的普通指针。为了能够处理push_back迭代器必须修改以处理进入向量的指针和位置,在通过运算符访问时,向量指针将被取消引用,指向获得数据的指针和添加的位置,并带有额外的取消引用。

虽然这不是最有效的实施方式,但这并不是一个真正的限制因素。VS/Dinkumware 库中迭代器的默认实现(即使在发布中)是经过检查的迭代器,它们管理等量的信息。

实际问题来自其他突变操作。考虑在矢量中间插入/擦除。为了保持所有迭代器的有效性,容器必须跟踪迭代器的所有实例并调整位置字段,以便它们仍然引用相同的元素(已入/删除所取代)。

您需要存储偏移量和指向矢量对象本身的指针。

如指定的那样,迭代器可以只是一个指针,占用的空间更少。

TL;DR -- 因为你正在用简单的无效规则换取更复杂的远距离操作规则。


请注意,"存储指向矢量对象的指针"会导致新的无效情况。 例如,今天swap保留迭代器有效性,如果指向向量的指针(或引用)存储在迭代器中,则不再可以。 所有移动矢量元数据本身的操作(矢量的矢量吗?)都会使迭代器无效。

您的交易是"当对元素的指针/引用无效时迭代器变得无效",因为"当对向量的指针/引用无效时迭代器变得无效"。

性能参数并不重要,因为提议的替代实现甚至不正确。

我一个迭代器没有失效,它应该指向相同的元素还是指向插入后的相同位置?换句话说,即使没有性能问题,决定使用哪个替代定义也并非易事。