Bug with std::deque?
Bug with std::deque?
我正在尝试使用循环和迭代器从deque中删除元素。我正在遵循在线示例,但看到一个错误。
我使用的是g++ (GCC) 4.8.3 20140911 (Red Hat 4.8.3-9)。
代码如下:
#include <iostream>
#include <deque>
using namespace std;
// Display the contents of a queue
void disp_deque(deque<int>& deque) {
cout << "deque contains: ";
for (auto itr = deque.begin(); itr!=deque.end(); ++itr)
cout << *itr << ' ';
cout << 'n';
}
int main(int argc, char** argv) {
deque<int> mydeque;
// Put 10 integers in the deque.
for (int i=1; i<=10; i++) mydeque.push_back(i);
disp_deque(mydeque);
auto it = mydeque.begin();
while (it!=mydeque.end()) {
cout << "Checking " << *it << ',';
// Delete even numbered values.
if ((*it % 2) == 0) {
cout << "Deleting " << *it << 'n';
mydeque.erase(it++);
disp_deque(mydeque);
} else ++it;
}
}
这很简单——创建一个包含10个元素的列表,然后删除偶数的元素。
请注意以下内容(不包括绒毛):
if ((*it % 2) == 0) {
mydeque.erase(it++);
} else it++;
建议使用迭代器进行删除,这样迭代器就不会像上面的链接中提到的那样失效。
然而,当我运行它时,我得到以下内容:
$ ./test
deque contains: 1 2 3 4 5 6 7 8 9 10
Checking 1,Checking 2,Deleting 2
deque contains: 1 3 4 5 6 7 8 9 10
Checking 3,Checking 4,Deleting 4
deque contains: 1 3 5 6 7 8 9 10
Checking 5,Checking 6,Deleting 6
deque contains: 1 3 5 7 8 9 10
Checking 7,Checking 8,Deleting 8
deque contains: 1 3 5 7 9 10
Checking 10,Deleting 10
deque contains: 1 3 5 7 9
Checking 10,Deleting 10
deque contains: 1 3 5 7
Checking 0,Deleting 0
deque contains: 1 3 5
Checking 0,Deleting 0
deque contains: 1 3
Checking 0,Deleting 0
deque contains: 1
Checking 0,Deleting 0
deque contains:
Checking 0,Deleting 0
Segmentation fault (core dumped)
通读一遍,直到它删除了8,它看起来还不错。事实上,第9条完全被跳过了,从来没有检查过!我所期望的应该是这样的:
$ ./test
deque contains: 1 2 3 4 5 6 7 8 9 10
Checking 1,Checking 2,Deleting 2
deque contains: 1 3 4 5 6 7 8 9 10
Checking 3,Checking 4,Deleting 4
deque contains: 1 3 5 6 7 8 9 10
Checking 5,Checking 6,Deleting 6
deque contains: 1 3 5 7 8 9 10
Checking 7,Checking 8,Deleting 8
deque contains: 1 3 5 7 9 10
Checking 9,Checking 10,Deleting 10
deque contains: 1 3 5 7 9
事实上,当我把代码改成这样时,我得到的就是这个:
if ((*it % 2) == 0) {
it=mydeque.erase(it);
} else it++;
那么,为什么一个方法有效,而另一个不行呢?有人能解释一下吗?
即使我创建一个临时迭代器来删除,我也会看到完全相同的问题输出:
while (it!=mydeque.end()) {
cout << "Checking " << *it << ',';
auto tmp_it = it++;
// Delete even numbered values.
if ((*tmp_it % 2) == 0) {
cout << "Deleting " << *tmp_it << 'n';
cout << "IT before delete: " << *it << 'n';
mydeque.erase(tmp_it);
cout << "IT after delete: " << *it << 'n';
disp_deque(mydeque);
}
}
这里我将它的副本存储在tmp_it中,然后对其进行自增。我添加了更多的调试语句,看到了一些非常奇怪的东西:
...
deque contains: 1 3 5 6 7 8 9 10
Checking 5,Checking 6,Deleting 6
IT before delete: 7
IT after delete: 7
deque contains: 1 3 5 7 8 9 10
Checking 7,Checking 8,Deleting 8
IT before delete: 9
IT after delete: 10
deque contains: 1 3 5 7 9 10
Checking 10,Deleting 10
IT before delete: 10
IT after delete: 10
...
然而,删除元素8使其指向元素10,跳过9!在之前的删除中,它指向前一个元素(例如,当6被删除时,它指向删除前后的7)。
我查找了deque的实现,在"Iterator Validity"下面看到了以下内容(强调我的):
Iterator validity擦除操作包含最后一个元素在序列中,end迭代器和迭代器、指针和引用已删除元素的引用无效。如果擦除包括第一个元素,但不包括最后一个元素引用被擦除的元素无效。如果发生在deque的其他地方,所有的迭代器,指针和引用
那么这是否意味着在我的代码中,我的迭代器是无效的,即使我在它被删除之前对它进行了后增量?即除了我删除的迭代器之外的迭代器正在失效?
如果是的话,那还好,但这似乎是一个鲜为人知的bug。这意味着在循环中删除迭代器的常见实现在使用deque时是无效的。
From cppreference on deque::erase()
:
所有迭代器和引用都无效,除非被删除的元素位于容器的末尾或开头,在这种情况下,只有迭代器和对被删除元素的引用无效。
迭代器。所有的。当你这样做时:
mydeque.erase(it++);
对it
进行后加并不重要,新迭代器也会失效。这就是为什么erase()
返回:
最后移除元素后的迭代器。如果迭代器pos指向最后一个元素,则返回end()迭代器。
所以你可以这样做:
it = mydeque.erase(it); // erase old it, new it is valid
尽管更好的方法是通过使用erase-remove习语来完全避免这个错误来源:
mydeque.erase(
std::remove_if(mydeque.begin(), mydeque.end(), [](int i){return i%2 == 0; }),
mydeque.end()
);
有关迭代器失效的更多信息,请参见此问题。
您引用的代码仅适用于关联容器 (set
, map
等)。
Scott Meyers的Effective STL条目9(恰当地命名为"在擦除选项中仔细选择")显示了序列容器 (vector, deque, string)
是如何完成的for (SeqContainer<int>::iterator it = c.beqin(); it != c.end();) {
if (predicate(*it)){
it = c.erase(it); // keep it valid by assigning
} // erase's return value to it
else ++it;
}
这里,erase()
的返回值正是我们需要的:它是a一旦擦除完成,指向被擦除元素后面的元素的有效迭代器。
那么这是否意味着在我的代码中,我的迭代器是无效的,即使我在它被删除之前对它进行了后增量?
这就是它的意思。该无效是否对结果有任何影响,这取决于运行时库中dequeue
的实现。它也可能在许多情况下进行得很顺利,然后突然失败,就像你的8。
后递增技巧只适用于容器,其中erase
只使指向已删除元素的迭代器失效(如list
s和set
s),在这些情况下,后递增创建指向下一个元素的迭代器,并在元素被擦除之前。因此,下一个元素的迭代器不受与擦除相关的失效的影响。然而,在dequeue
的情况下,规范规定所有迭代器无效。
c++ 14引入了通用的erase_if
算法,该算法可以正确地工作于所有类型的标准容器。
这相当于@Barry提供的最后一个代码块:
#include <experimental/deque>
std::experimental::erase_if(mydeque, [](int i){return i%2 == 0; });
与直接擦除/删除-if模式相比,使用这种泛型算法也更好,因为如果您决定将容器替换为std::set
,例如,您将不需要更新处理删除的代码。
- 如何仅将新添加的元素复制到std :: Deque中
- 为什么STD :: Deque子阵列尺寸固定
- 为什么std::vector比std::deque更受欢迎
- STD Deque出奇地慢
- c++11移动std::deque或std::list的插入
- 将std::deque的现有元素推到前面
- 使用 std::stack 而不仅仅是 deque、vector 或 list 有什么优点和缺点
- C++std::从std::deque复制到std:;设置
- std::deque::erase函数的时间复杂度是多少
- 如何在Visual studio 2010中查看存储在std::deque中的内容(调试模式)
- 类的const成员出现std::deque::erase编译错误
- 'deque itrator not dereferencable' 而做 std::d eque->front()
- 对std::deque执行迭代器在容器两端插入或擦除后会失效
- 一个线程读一个线程写,std::deque是安全的
- std::deque::push_back/front的复杂度要求
- c++ std::deque实现:为什么不使用循环缓冲区
- 为什么std::deque的效率这么差?
- 为什么std::deque在默认构造函数中为它的元素分配内存?
- Bug with std::deque?
- 如何获取指向原始数据的std::deque指针