6

给出下面的代码(比如说它的名字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应该在无效的迭代器上操作。但是为什么代码不会导致错误?

4

2 回答 2

5

这里的问题源于反向迭代器实际上是什么。反向迭代器的相关关系是:

r对于从 iterator 构造的反向迭代器i,关系&*r==&*(i-1)始终为真(只要 r 是可解引用的);因此,从一个过去的迭代器构造的反向迭代器取消对序列中最后一个元素的引用。

当我们这样做时std::deque::pop_back(),我们使:

对已擦除元素的迭代器和引用无效。过去的迭代器也无效。其他引用和迭代器不受影响。

rbegin()由 构成end()it在我们第一次递增之后,it将取消对 the 的引用,2但它的基础基础迭代器指向3- 那是被擦除的元素。所以引用它的迭代器包括你现在先进的反向迭代器。这就是它失效的原因,这就是为什么您会看到您所看到的错误。

反向迭代器很复杂。


您可以将其重新分配itrbegin()

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();
}
于 2016-05-17T16:06:16.430 回答
1

从底层容器中擦除会使迭代器无效。引用规则:

迭代器是不可解引用的,如果

  • 它们是结束后的迭代器(包括超出数组末尾的指针)或开始前的迭代器。这样的迭代器在特定实现中可能是可取消引用的,但库从不假定它们是可取消引用的。
  • 它们是单数迭代器,即不与任何序列关联的迭代器。空指针以及默认构造的指针(保存不确定值)是单数
  • 它们被它们引用的序列上的迭代器无效操作之一无效。

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

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

于 2016-05-17T15:52:50.450 回答