【问题标题】:Got singular iterator error in looping with iterator and pop_back使用迭代器和 pop_back 循环时出现奇异迭代器错误
【发布时间】:2016-09-13 19:50:02
【问题描述】:

给出下面的代码(假设它被命名为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 应该在一个无效的迭代器上运行。但是为什么代码不会报错呢?

【问题讨论】:

  • 这是否意味着反向迭代器实际上并不指向我期望的节点,而是指向它之后的节点? 是的!
  • 如果有人来到这里想知道“singular iterator 到底是什么,我为什么要害怕引用它”:我在 IRC 上被推荐为 this link。 C++ 标准似乎将“奇异”值称为无效值。 “单数迭代器”仅仅意味着它指向 la-la land,无论出于何种原因(无论它是从一开始就未初始化,还是被erase() 操作无效) .

标签: c++ c++11 iterator reverse-iterator


【解决方案1】:

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

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

  • 它们是过去的迭代器 (包括超出数组末尾的指针)或before-begin 迭代器。这样的迭代器可以在特定的 实现,但库从不假定它们是。
  • 它们是单数迭代器,即不与任何序列关联的迭代器。一个空指针,以及一个默认构造的指针 (持有一个不确定的值)是单数
  • 它们被无效 序列上的迭代器无效操作之一 他们参考。

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

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

【讨论】:

  • 具体的问题是他增加了迭代器before pop_back()而不是after
  • 据称哪个迭代器失效了? ++it 发生在 pop_back() 之前
  • 那为什么for (auto it = d.rbegin(); it != d.rend();++it)也能避免这个错误呢?
  • @sleeptightpupper - 迭代器在循环迭代中失效'pop_back'之后
  • @Barry - 对您的评论与上述相同。请重新考虑投票
【解决方案2】:

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

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

当我们随后执行std::deque::pop_back() 时,我们将无效:

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

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

【讨论】:

  • 我看不出为什么一个被擦除元素的迭代器仍然不能作为一个过去的迭代器有效。这里的库或标准是不是过于保守了?
  • @filipos “我看不出理由”通常不是一个合理的论点。
  • 这当然不是反对您的答案的论据。但是从库设计的角度来看,您不希望不必要地使迭代器无效,以免限制使用。因此,这是一个自然的问题。例如,如果您考虑将迭代器实现为指针的向量(同样的情况适用),则指向最后一个元素的指针在删除后将成为有效的结束指针。
  • 很高兴知道reverse_iterator是基于iterator实现的,实际上document确实这么说但我不知道。 原始迭代器(基迭代器)的副本保存在内部,用于反映在 reverse_iterator 上执行的操作:每当 reverse_iterator 递增时,其基迭代器就会减少,反之亦然。跨度>
  • 但是为什么for (auto it = d.rbegin(); it != d.rend();++it) { d.pop_back();} 会避免这个错误呢?在d.pop_back()之后,为什么++it没有触发错误?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-03-27
  • 1970-01-01
  • 2020-10-23
  • 2015-02-08
  • 1970-01-01
  • 2011-10-24
相关资源
最近更新 更多