【问题标题】:Why does this vector iterator not become invalidated?为什么这个向量迭代器不会失效?
【发布时间】:2014-05-17 10:51:39
【问题描述】:

我已经阅读了一些关于迭代器失效的帖子,似乎需要向量重新分配的插入会使迭代器失效。向量中间的擦除也不应该导致失效吗?

我对此没有清楚的理解,不知道为什么在调整大小并从开头、中间和结尾删除后使用这些迭代器不会破坏它们:

#include <cstdlib>
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(int argc, char** argv) {

    vector<int> v;
    v.reserve(10);

    for (int i = 0; i < 10; i++)
        v.push_back(i);

    for (auto x = v.begin(); x != v.end(); x++) {
        cout << *x << endl;
    }

    cout << endl << "RESIZE" << endl << endl;

    for (int i = 10; i < 20; i++)
        v.push_back(i);

    for (auto x = v.begin(); x != v.end(); x++) {
        cout << *x << endl;
    }

    cout << endl << "RESIZE 2" << endl << endl;

    for (int i = 20; i < 200; i++)
        v.push_back(i);

    for (auto x = v.begin(); x != v.end(); x++) {
        cout << *x << endl;
    }

    cout << endl << "REMOVES" << endl << endl;

    v.erase(v.begin());
    v.pop_back();
    v.erase(v.begin() + 17);

    for (auto x = v.begin(); x != v.end(); x++) {
        cout << *x << endl;
    }

    return 0;
}

【问题讨论】:

  • 迭代器的有效性取决于容器的类型和操作。例如,对于 vector::erase,检查 cplusplus.com/reference/vector/vector/erase 的迭代器有效性部分
  • @Instinct 我没有看到任何迭代器的重用在这里进行。您能否指出您认为发生这种情况的线路?
  • 在您的代码中,您每次迭代容器时都会获取新的迭代器,因此您并没有真正测试您提到的擦除和重新调整大小的情况。当您将 b.begin() 的返回值复制到迭代器类型,然后调用 v.push_back(...) 几次,然后尝试使用迭代器时,就会发生迭代器失效。向量具有逻辑大小和物理大小 s.t.物理 >= 逻辑。
  • 向量是用连续内存实现的,所以当 push_back(...) 导致逻辑大小大于当前物理大小时,必须将底层内存复制到更大的连续内存块.迭代器有一个指向这个内存的内部指针,所以当这个“增长”操作发生时,迭代器现在在内部引用一块不再是向量一部分的内存,因此它们是无效的。
  • 在初始化迭代器后尝试调用 v.capacity() 并将返回值存储在某个临时位置。 v.capacity() 返回向量中当前分配的元素总数,并且大于或等于 v.size()。现在调用 v.push_back(...) 直到向量的大小超过从存储在临时值中的 capacity() 返回的值。当您将大小增加到超过之前的容量时,向量会增长并且迭代器会失效。

标签: c++ vector stl iterator invalidation


【解决方案1】:

请注意,调用 begin() 或 end() 将始终提供合理的迭代器

不过是这样的:

std:vector<int> v;
....

std::vector<int>::iterator i=v.begin();
v.erase(i);
std::cout << *i << std::endl;  // i iterator was invalidated by erasing it
                               // trying to access it or increment it is undefined behaviour.
std::cout << *v.begin() << std::endl;  // begin() provides always a sane iterator.

【讨论】:

  • 一般来说,我总是假设它不会起作用:)...只有当我需要做一些花哨的事情时,我才会检查每个容器和每个操作的规则... ;)
  • 在 UB 的情况下,如果它“有效”,它实际上是无效的。 “It works”是一个表达方式,表示某事做了/它打算做什么/。使用 UB,/没有预期的行为/因此,您不能声称它做到了。
  • @sehe:实际上,天真无知的程序员所期望的正是这些。至少这是我能想到的对它有效最合理的定义。
  • @sehe 和 Deduplicator... 更改了措辞... 从 too colloquial 工作/不工作... :D
  • @jsantander:我喜欢看到理智统治。 +1
【解决方案2】:

在您的代码中,总是在重复使用迭代器时,没有对向量进行干预修改,因此没有失效。

迭代器在调整大小和插入时可能会失效。擦除只会使被擦除元素处或之后的迭代器无效。

至少,这些是std::vector 的释义规则。

【讨论】:

  • 究竟什么才是“干预性修改”?我认为从中间擦除肯定会,并强制将所有元素的矢量调整大小/复制到一个新的更大的数组?
  • 是的,任何可能改变元素计数的东西。此外,明确保留空间。
  • 实际上,删除也会使除已删除迭代器之外的所有迭代器失效。见Iterator Invalidation Rules
  • @Deduplicator 那么为什么当x 指向v.begin() 时擦除第一个元素不会使其无效?假设我没有像在原始帖子中那样重新声明 x,因为出于某种原因,在原始 10 个槽向量填充之后立即声明 auto x = v.begin(); 并从所有未来循环 x 中删除 auto 以不重新声明仍然可以正常工作?
  • 它确实无效。但这并不能保证崩溃。另外,如果你分配一个新的,旧的就没有了……
猜你喜欢
  • 2011-04-16
  • 2015-07-31
  • 2013-05-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多