给出以下代码(比如说它名为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
应该在无效的迭代器上运行。但为什么代码不会导致错误?
答案 0 :(得分:5)
这里的问题源于反向迭代器实际上是什么。 reverse iterator的相关关系是:
对于从迭代器
r
构造的反向迭代器i
,关系&*r
==&*(i-1)
始终为真(只要r是可解除引用的);因此,一个反向迭代器由一个过去的结束迭代器构造,引用序列中的最后一个元素。
当我们执行std::deque::pop_back()
时,我们会失效:
对擦除元素的迭代器和引用无效。过去的迭代器也会失效。其他引用和迭代器不受影响。
rbegin()
由end()
构成。在我们第一次递增it
之后,it
将取消引用2
但其底层基础迭代器指向3
- 这是已擦除的元素。所以引用它的迭代器包括你现在先进的反向迭代器。这就是为什么它失效了,这就是你看到你所看到的错误的原因。
反向迭代器很复杂。
您可以将其重新分配给it
,而不是递增rbegin()
:
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();
}
答案 1 :(得分:0)
从底层容器中擦除使迭代器无效。引用规则:
如果
,迭代器不可解除引用
- 他们是过去的终极迭代者 (包括指向数组末尾的指针)或者在开始之前 迭代器。这样的迭代器可以在特定的情况下是可解除引用的 实现,但库从不假设它们是。
- 它们是单数迭代器,即与任何序列无关的迭代器。空指针,以及默认构造的指针 (持有不确定的价值)是单数的
- 他们被宣告无效 对序列的迭代器无效操作之一 他们参考。
您的代码导致迭代器被pop_back
操作无效,因此根据上面的第三点,它变为不可解除引用。
在while
循环中,通过在每次循环重复中获取(新)有效迭代器来避免此问题。