在使用迭代器和 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();
}
给出下面的代码(假设它被命名为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 iteratori
, 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();
}