C++11 中的过去终结迭代器失效

Past-the-end iterator invalidation in C++11

本文关键字:迭代器 失效 过去 C++11      更新时间:2023-10-16

C++迭代器失效规则上最受欢迎的帖子声称,目前尚不清楚过去结束的迭代器(即由end()cend()rend()crend()返回的迭代器)是否根据与普通迭代器相同的规则失效,这些规则指向容器中的元素。这些针对 2003 年和 2011 年C++提出的声明都遵循了讨论 End 迭代器无效规则的帖子,其中接受的答案表明 2003 年标准在这个问题上模棱两可。这个结论是基于 23.1/10 中的评论(在 swap() 的上下文中),该评论似乎暗示当规范没有明确提到过去结束迭代器的无效时,它们可能会失效。

对这篇文章问题的评论(由迈克-西摩提出)表明,就deque而言,C++11在这件事上是明确的。我的问题是关于所有容器的:

  • 在 C++11 中,是否有任何容器操作可能会使过去结束迭代器无效,以及此行为在语言规范中不明确?

换一种说法,

  • 在执行容器操作后,我是否可以信任过去结束迭代器的有效性,该容器操作并未说明它可能使过去结束迭代器无效?

我的问题是关于所有容器的:

  • 在 C++11 中,是否有任何容器操作可能会使过去结束迭代器无效,以及此行为在 语言规范?

我不确定你所说的"这种行为在语言规范中不明确"是什么意思,但肯定有一些操作会使过去的运算符无效(例如插入到std::vectorstd::string中)。

换一种说法,

  • 在执行未说明可能无效的容器操作后,我是否可以信任过去结束迭代器的有效性 过去的迭代器?

您可以像信任任何其他迭代器一样信任过去结束迭代器:任何不会(可能)使迭代器无效的操作都不会使它们失效。除了标准存在错误的可能性之外,这是所有没有说它们(可能)使操作员无效的操作。

如果标准说该操作不会使迭代器无效,您应该能够信任它。其他任何内容都应被视为标准库实现中的错误。

关于结束迭代器的失效规则,在 cppreference.com#Iterator_invalidation 中有一个提及。

相关行是:

过去的迭代器值得特别一提。通常,此迭代器是无效的,就好像它是未擦除元素的普通迭代器一样。因此,std::set::end 永远不会失效,std::unordered_set::end 仅在重新哈希时失效,std::vector::end 总是无效(因为它总是在修改的元素之后),依此类推。

有一个例外:删除 std::d eque 最后一个元素的擦除确实会使过去结束迭代器无效,即使它不是容器的擦除元素(或根本不是元素)。结合 std::d eque 迭代器的一般规则,最终结果是,唯一不会使 std::d eque::end 无效的修改操作是删除第一个元素,而不是最后一个元素。

另请参阅使用 std::set 的 end() - https://godbolt.org/z/5ecdqYod3 进行测试。

至少在GCC中,迭代器因std::map而失效:

#include <set>
#include <stdlib.h>
#include <assert.h>
int main() {
  std::set<int> a;
  a.insert(1);
  std::set<int>::reverse_iterator rit(a.rbegin());
  ++rit;
  assert(rit==a.rend());
  a.erase(a.begin());
  assert(a.rend()==rit); // FAIL
}