【问题标题】:Does resizing a vector invalidate iterators?调整向量大小是否会使迭代器无效?
【发布时间】:2010-12-10 03:15:03
【问题描述】:

我发现这个 C++ 代码:

vector<int> a;
a.push_back(1);
a.push_back(2);
vector<int>::iterator it = a.begin();
a.push_back(4);
cout << *it;

打印一些大的随机数;但是如果你在第 3 行和第 4 行之间添加a.push_back(3),它会打印 1。你能给我解释一下吗?

【问题讨论】:

  • 当我有这样的问题时,一个快速的谷歌可以回答它。谷歌搜索“std vector push_back”可以引导您to here,如果您阅读它,它会说“如果新的 size() 大于 capacity(),那么所有迭代器和引用(包括过去的迭代器)都是无效。否则只有过去的迭代器无效。"

标签: c++ vector iterator


【解决方案1】:

用更谨慎的措辞编辑

是的,调整向量的大小可能会使指向向量的所有迭代器无效。

向量是通过在内部分配一个存储数据的数组来实现的。当向量增长时,该数组可能会耗尽空间,当它发生时,向量会分配一个更大的新数组,将数据复制到该数组,然后删除旧数组。

因此,指向旧内存的旧迭代器不再有效。 但是,如果向量被向下 调整大小(例如通过pop_back()),则使用相同的数组。数组永远不会自动缩小。

避免这种重新分配(和指针失效)的一种方法是首先调用vector::reserve(),以留出足够的空间,这样就不需要进行复制了。在您的情况下,如果您在第一个 push_back() 操作之前调用了 a.reserve(3),那么内部数组将足够大,以至于无需重新分配数组即可执行 push_back,因此您的迭代器将保持有效.

【讨论】:

  • 您的第一句话与您的最后一段不匹配。如果您将向量的大小调整为小于先前保留调用保留的容量,则可以保证调整大小之前的有效迭代器保持有效。所以:“调整向量的大小可能会使指向向量的所有迭代器无效。”
  • 情况是这样的:发生失效如果新添加的空间超出了保留空间并且新的低级分配位于不同的部分内存(因为允许低级分配器尝试在适当的位置增长块)。但根据设计std::vector() 会阻止您找出这些条件是否适用。因此,确保迭代器在 push_back() 之后保持有效的唯一方法是提前手动保留足够的空间。
  • 其实,你可以在大多数实现上检查“容量”,但我不知道它是否符合标准。
  • 没错,“调整大小”可能不是最佳选择。向下调整大小不会导致任何问题,向上调整大小可能没问题(如果“调整大小”是指resize() 函数,我们之前称为reserve()
  • 我认为 Matthieu M. 是对的,但现在我不太确定了。该标准规定,如果新大小大于容量,insert(因此,通过引用,push_back)会导致重新分配。然后继续说,如果没有发生重新分配,则插入点之前的迭代器(push_back 的所有元素的迭代器)仍然有效。不幸的是,该标准似乎没有说明相反的情况,即它没有说“如果新大小不大于容量,则不会发生重新分配”,除非在其他地方暗示这一点。
【解决方案2】:

向量迭代器仅在向量执行重新分配时失效。

push_back(4) 的调用导致向量分配新的内存块 - 这就是导致迭代器失效的原因。当您还使用push_back(3) 时,不会对push_back(4) 执行重新分配,因此迭代器仍然有效。

【讨论】:

    【解决方案3】:

    是的,任何可能改变向量大小的动作都会使迭代器失效。

    编辑:这包括减小容器大小的操作(例如erase()resize())。 erase() 不会使 all 迭代器失效,但它确实使任何引用被擦除元素之后的任何点的迭代器失效。 resize()是由insert()erase()定义的,所以具有相同的潜力。

    【讨论】:

      【解决方案4】:

      迭代器失效的规则是特定于容器的。

      现在失效可能对向量有 2 个含义:

      1. 无效 = 点超出 [begin,end] 定义的范围
      2. 无效 = 指向与原始对象不同的对象

      如您所见,第二个要严格得多:

      std::vector<int> myVector;
      myVector.push_back(0);
      myVector.push_back(1);
      
      std::vector<int>::iterator it = myVector.begin(); // it points to 0
      myVector.erase(it); // it points to 1
      myVector.erase(it); // it == myVector.end()
      

      在这种情况下,它是“有效的”,因为它始终在包含范围内 [begin,end],因此可以安全地用于 myVector 上的任何操作。另一方面,表达式 (*it) 不断变化:首先它返回 0,然后返回 1,然后它具有未定义的行为...

      一般来说,人们更愿意谈论第二个要求,而使迭代器无效仅仅意味着 (*it) 可能不会产生与以前相同的结果。


      说到这里,有几种方法可以使 Vector 上的迭代器无效(实际上,它是 STL 的不太稳定的结构)。

      在添加元素期间:

      • 如果 myVector.size() == myVector.capacity(),这可能会触发重新分配 (1),因为检查这很容易出错,我们通常认为任何添加都会使迭代器无效
      • 如果你想'挑剔'并且知道不会触发重新分配,那么你仍然需要担心insert。插入一个元素会使指向当前位置的迭代器以及所有后续位置的迭代器失效,因为这些元素向向量末尾移动了一步。

      在移除元素期间:

      • 没有重新分配,即使缓冲区现在比需要的大得多。不过,可以使用 shrink to fit 成语 (2) 来强制执行此操作。
      • 所有指向被删除元素的迭代器都无效。特别是,之前的“end”迭代器现在超出了 [begin,end] 范围,无法在 STL 算法中安全使用。

      (1) std::vector 的内部结构是一个 T 数组,这是为了与 C 程序兼容(使用 &myVector.front() 作为数组的地址)并且因为它保证了连续性以及最小的空间开销(即向量自身数据占用的空间量与对象占用的空间量)

      在任何时候,您都可以使用 .capacity() 方法知道一个向量可以容纳多少个对象。

      当您想要插入一个对象并且向量没有必要的容量时,会触发对 .reserve(size_t) 方法的调用。此方法,如果所需的项目数优于当前容量,则会触发重新分配

      向量然后分配一个新的元素数组(它的大小一般为2*n+1,其中n是当前容量),将当前数组的元素复制到新数组中,丢弃当前数组。

      因为它丢弃了当前数组,所以您的迭代器会失效,因为向量迭代器通常是简单的指针(为了提高效率)。

      请注意,如果迭代器被实现为:对向量的引用 + 计数,并且取消引用实际上是 *(&m_vector.front() + n) 重新分配不会使它们无效......但它们的效率会降低。

      (2) 缩小以适应:警告,这会触发元素的复制并使迭代器无效。

      // myVector has 10 elements, but myVector.capacity() == 1000
      myVector.swap(std::vector<int>(myVector));
      

      它首先创建一个临时向量,该向量将只分配所需的内存(最小值取决于库),然后复制 myVector 的元素。然后交换操作从 myVector 和这个副本交换缓冲区,因此 myVector 现在拥有一个具有所需最小内存量的缓冲区。在操作结束时,临时对象被销毁并释放它所持有的内存。

      【讨论】:

        【解决方案5】:

        为了将来参考,所有像这样的 STL 类型的花絮都在 SGI 的网站上:http://www.sgi.com/tech/stl/Vector.html

        如果您需要迭代器在向集合添加或删除后保持有效,请查看另一种集合,例如列表。

        最好的办法是从集合(随机访问等)中识别出您想要的特征矩阵,然后选择正确的容器。

        请参阅有关 Standard_Template_Library Containers 的维基百科文章作为起点。如果你有钱,我强烈推荐 Scott Meyer 的“有效的 STL:50 种改进标准模板库使用的具体方法”。

        抱歉缺少支持链接,我是这里的新手,缺乏与多个人一起发布此内容的声誉。

        【讨论】:

          猜你喜欢
          • 2011-04-22
          • 2012-06-16
          • 2011-01-08
          • 1970-01-01
          • 1970-01-01
          • 2016-10-14
          • 2023-04-04
          • 1970-01-01
          相关资源
          最近更新 更多