在使用迭代器和 pop_back 循环时出现单一迭代器错误

Got singular iterator error in looping with iterator and pop_back

给出下面的代码(假设它被命名为deque.cpp

#include <cstdio>
#include <deque>

int main()
{
  std::deque<int> d = {1, 2, 3};
  for (auto it = d.rbegin(); it != d.rend();) {
    printf("it: %d\n", *it);
    ++it;
    d.pop_back();
  }
  return 0;
}

g++ -std=c++11 -o deque deque.cpp编译,运行良好:

$ ./deque
it: 3
it: 2
it: 1

但是如果用-D_GLIBCXX_DEBUG编译(g++ -std=c++11 -o deque_debug deque.cpp -D_GLIBCXX_DEBUG,它会出现以下错误:

$ ./deque_debug
it: 3
/usr/include/c++/4.8/debug/safe_iterator.h:171:error: attempt to copy-
    construct an iterator from a singular iterator.
...

看起来第二个循环的 ++it 是从单个迭代器构建的。 但是我觉得在第一个循环的++it之后,迭代器指向了2,pop_back()不应该让它失效。那为什么会报错呢?

注意:我知道代码可以重写如下:

  while (!d.empty()) {
    auto it = d.rbegin();
    printf("it: %d\n", *it);
    d.pop_back();
  }

错误将消失。

但我确实想知道错误代码到底发生了什么。 (这是否意味着反向迭代器实际上并不指向我期望的节点,而是指向它之后的节点?)


更新: @Barry 的回答解决了这个问题。 请让我提出一个额外的相关问题:代码

  for (auto it = d.rbegin(); it != d.rend();) {
    printf("it: %d\n", *it);
    d.pop_back();
    ++it;   // <== moved below pop_back()
  }

应该是错误的,其中 ++it 应该在无效的迭代器上运行。但是为什么代码没有报错呢?

从底层容器中删除会使迭代器失效。引用自规则:

Iterators are not dereferenceable if

  • they are past-the-end iterators (including pointers past the end of an array) or before-begin iterators. Such iterators may be dereferenceable in a particular implementation, but the library never assumes that they are.
  • they are singular iterators, that is, iterators that are not associated with any sequence. A null pointer, as well as a default-constructed pointer (holding an indeterminate value) is singular
  • they were invalidated by one of the iterator-invalidating operations on the sequence to which they refer.

您的代码导致迭代器因 pop_back 操作而失效,因此根据上面的第三点,它变得不可取消引用。

在您的 while 循环中,您通过在每次循环重复中获得一个(新的)有效迭代器来避免这个问题。

这里的问题出在反向迭代器实际上是什么。 reverse iterator的相关关系是:

For a reverse iterator r constructed from an iterator i, the relationship &*r == &*(i-1) is always true (as long as r is dereferenceable); thus a reverse iterator constructed from a one-past-the-end iterator dereferences to the last element in a sequence.

当我们执行 std::deque::pop_back() 时,我们使:

Iterators and references to the erased element are invalidated. The past-the-end iterator is also invalidated. Other references and iterators are not affected.

rbegin() 是从 end() 构造的。在我们第一次递增 it 之后,it 将取消对 2 的引用,但其底层基础迭代器指向 3 - 这就是被擦除的元素。所以引用它的迭代器包括你现在高级的反向迭代器。这就是它无效的原因,也是您看到所看到的错误的原因。

反向迭代器很复杂。


您可以将其重新分配给 rbegin():

,而不是递增 it
for (auto it = d.rbegin(); it != d.rend();) {
    printf("it: %d\n", *it);
    d.pop_back();
    // 'it' and 'it+1' are both invalidated
    it = d.rbegin();
}