调整向量大小会使迭代器无效吗?

时间:2009-10-26 13:30:31

标签: c++ vector iterator

我发现这个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.你能解释一下吗?

5 个答案:

答案 0 :(得分:28)

用更谨慎的措辞编辑

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

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

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

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

答案 1 :(得分:7)

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

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

答案 2 :(得分:3)

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

修改:包括减少容器大小的操作(例如erase()resize())。 erase()不会使所有迭代器无效,但它会使引用已擦除元素之后的任何点的任何迭代器无效。 resize()是根据insert()erase()定义的,因此具有相同的潜力。

答案 3 :(得分:1)

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

现在,无效可能有2个含义:

  1. 无效=超出[begin,end]
  2. 定义的范围
  3. 无效=指向与原始对象不同的对象
  4. 如您所见,第二个更为严格:

    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()
    

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

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


    既然如此,有几种方法可以使Vector上的迭代器无效(实际上,它是STL不太稳定的结构)。

    在添加元素期间:

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

    删除元素期间:

    • 即使缓冲区现在比需要的大得多,也没有重新分配。但是,可以使用 shrink to fit idiom(2)强制执行此操作。
    • 指向已删除元素的所有迭代器都将失效。特别是,之前的'end'迭代器现在超出了[begin,end]范围,并且不能在STL算法中安全使用。例如。

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

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

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

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

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

    请注意,如果迭代器实现为:对向量的引用+计数,并且解除引用实际上是*(&amp; m_vector.front()+ n)重新分配不会使它们失效...但它们会更少高效。

    (2)缩小到适合:警告,这会触发元素的COPY并使迭代器无效。

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

    它首先创建一个临时向量,它将根据需要分配尽可能多的内存(最小值取决于库),并复制myVector的元素。然后交换操作从myVector和此副本交换缓冲区,因此myVector现在支持一个具有所需最小内存量的缓冲区。在操作结束时,临时被破坏并且它保存的内存被释放。

答案 4 :(得分:0)

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

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

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

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

对于缺乏支持链接的抱歉,我在这里是一个新手,并且缺少与多个人发布此信息的声誉。