我不明白为什么 avector
的迭代器在重新分配发生时应该失效。
难道不能简单地通过在迭代器中存储一个偏移量——而不是一个指针——来防止这种情况吗?
为什么vector
不是这样设计的?
我不明白为什么 avector
的迭代器在重新分配发生时应该失效。
难道不能简单地通过在迭代器中存储一个偏移量——而不是一个指针——来防止这种情况吗?
为什么vector
不是这样设计的?
只是为与性能相关的理由添加一个引用:在设计 C++ 时,Stroustrup 认为像std::vector这样的模板类接近原生数组的性能特征是至关重要的:
强调运行时效率的一个原因是……我希望模板在时间和空间上足够高效,以便用于数组和列表等低级类型。
...
更高级别的替代方案——例如,具有 size() 操作的范围检查数组、多维数组、具有适当数字向量操作和复制语义的向量类型等——只有在他们的运行时才会被用户接受 -时间、空间和符号的便利性接近了内置数组。
换句话说,提供参数化类型的语言机制应该是这样的,相关用户应该有能力消除数组的使用,转而使用标准库类。
Bjarne Stroustrup,C++ 的设计和演变,第 342 页。
因为迭代器要做到这一点,他们需要存储一个指向向量对象的指针。对于每个数据访问,他们需要跟随指向向量的指针,然后跟随其中的指针指向数据数组的当前位置,然后添加偏移量 * 元素大小。那会慢得多,并且需要为size_type
成员提供更多内存。
当然,有时这是一个很好的折衷方案,并且能够在需要时选择它会很好,但它比(C 风格)直接使用数组更慢、更笨重。 std::vector
在引入 STL 时,对性能进行了无情的审查,并且正常的实现针对空间和速度进行了优化,超过了这个便利性/可靠性因素,就像等效数组与数组operator[]
一样快,但安全性低于at()
.
您可以通过包装标准来增加安全性std::vector<T>::iterator
,但不能通过包装来增加速度extension::vector<T>::safe_iterator
。这是一般原则,并解释了许多 C++ 设计选择。
这些决定有很多原因。正如其他人指出的那样,向量的最基本实现iterator
是指向元素的普通指针。为了能够处理push_back
迭代器,必须修改以处理指向向量和位置的指针,在通过运算符访问时,必须取消引用向量指针,获得指向数据的指针并添加位置,并带有额外的取消引用。
虽然这不是最有效的实施方式,但这并不是真正的限制因素。VS/Dinkumware 库(甚至在发行版中)中迭代器的默认实现是检查迭代器,它管理等量的信息。
实际问题来自其他变异操作。考虑在矢量中间插入/擦除。为了保持所有迭代器的有效性,容器必须跟踪迭代器的所有实例并调整位置字段,以便它们仍然引用相同的元素(已被插入/删除替换)。
您需要存储偏移量和指向矢量对象本身的指针。
如指定的那样,迭代器可以只是一个指针,它占用的空间更少。
TL;DR——因为您正在用简单的无效规则换取更复杂的远距离操作规则。
请注意,“存储指向向量对象的指针”会导致新的失效情况。例如,今天swap
保留了迭代器的有效性,如果指向向量的指针(或引用)存储在迭代器中,则它不再可以。所有移动向量元数据本身的操作(向量的向量有人吗?)将使迭代器无效。
您交易的是“当对元素的指针/引用无效时迭代器变得无效”为“当对向量的指针/引用无效时迭代器变得无效”。
性能参数并不重要,因为建议的替代实现甚至都不正确。
我的迭代器没有失效,它应该指向同一个元素还是在它之前的插入之后指向同一个位置?换句话说,即使没有性能问题,决定使用哪个替代定义也并非易事。