迭代器失效的规则是特定于容器的。
现在失效可能对向量有 2 个含义:
- 无效 = 点超出 [begin,end] 定义的范围
- 无效 = 指向与原始对象不同的对象
如您所见,第二个要严格得多:
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 现在拥有一个具有所需最小内存量的缓冲区。在操作结束时,临时对象被销毁并释放它所持有的内存。