简体   繁体   English

std :: deque的错误?

[英]Bug with std::deque?

I am trying to delete an element from a deque using a loop and an iterator. 我试图使用循环和迭代器从双端队列中删除一个元素。 I am following online examples but seeing a bug. 我正在关注在线示例,但看到了一个错误。

I am using g++ (GCC) 4.8.3 20140911 (Red Hat 4.8.3-9). 我正在使用g ++(GCC)4.8.3 20140911(Red Hat 4.8.3-9)。

Here is the code: 这是代码:

#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;
  }
}

It is pretty straight forward - create a list of 10 elements and delete the even ones. 这很简单 - 创建一个包含10个元素的列表并删除偶数元素。

Notice the following (fluff excluded): 请注意以下内容(不包括绒毛):

if ((*it % 2) == 0) {
  mydeque.erase(it++);
} else it++;

It is the recommended way to delete using an iterator so that your iterator does not get invalidated as mentioned in the link above. 这是使用迭代器进行删除的推荐方法,这样您的迭代器就不会像上面的链接中提到的那样失效。

However, when I run this, I get the following: 但是,当我运行它时,我得到以下内容:

$ ./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)

Looking through it, it seems pretty good up until it deletes 8. In fact, number 9 is skipped altogether and never checked! 看一看,它看起来很不错,直到删除8.实际上,数字9被完全跳过并且从未检查过! What I expect should happen is this: 我期望应该发生的是:

$ ./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 

In fact, this is exactly what I get when I change the code to this: 事实上,这正是我将代码更改为此时所获得的:

if ((*it % 2) == 0) {
  it=mydeque.erase(it);
} else it++;

So, why does one method work, but the other not? 那么,为什么一种方法有效,而另一种方法无效? Can anyone explain it? 有人能解释一下吗?

Even if I create a temporary iterator to delete, I see the exact same problem output: 即使我创建一个要删除的临时迭代器,我也会看到完全相同的问题输出:

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

Here I store a copy of it in tmp_it and then increment it. 在这里,我将它的副本存储在tmp_it中,然后递增它。 I added some more debug statements and saw some really weird stuff: 我添加了一些调试语句,看到了一些非常奇怪的东西:

...
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
...

However, the deletion of element 8 makes it point to element 10, skipping 9! 但是,删除元素8使其指向元素10,跳过9! On previous deletes, it was pointing to the previous element (eg when 6 was deleted, it was pointing to 7 before and after the delete). 在先前的删除中,它指向前一个元素(例如,当删除6时,它指向删除之前和之后的7)。

I looked up the implementation of deque and see under "Iterator Validity" the following (emphasis mine): 我查看了deque的实现,并在“Iterator Validity”下面看到了(强调我的):

Iterator validity If the erasure operation includes the last element in the sequence, the end iterator and the iterators, pointers and references referring to the erased elements are invalidated. 迭代器有效性如果擦除操作包括序列中的最后一个元素,则结束迭代器以及引用已擦除元素的迭代器,指针和引用无效。 If the erasure includes the first element but not the last, only those referring to the erased elements are invalidated. 如果擦除包括第一个元素但不包括最后一个元素,则只有那些涉及擦除元素的元素无效。 If it happens anywhere else in the deque, all iterators, pointers and references related to the container are invalidated. 如果它发生在deque中的任何其他位置,则与容器相关的所有迭代器,指针和引用都将失效。

So does that mean that in my code, my iterator is being invalidated even though I did a post increment on it before it is deleted? 那么这是否意味着在我的代码中,即使我在删除之前对其进行了后期增量,我的迭代器也会失效? ie an iterator other than the one I deleted is being invalidated? 即除了我删除的迭代器之外的迭代器是无效的?

If so, then that it fine, but it seems like a little known bug. 如果是这样,那么它很好,但它似乎是一个鲜为人知的bug。 It means that common implementations of iterator deletion within a loop are not valid when using deque. 这意味着在使用deque时,循环中迭代器删除的常见实现无效。

From cppreference on deque::erase() : 来自deque::erase() cppreference:

All iterators and references are invalidated , unless the erased elements are at the end or the beginning of the container, in which case only the iterators and references to the erased elements are invalidated. 除非擦除的元素位于容器的末尾或开头,否则所有迭代器和引用都将失效 ,在这种情况下,只有迭代器和对擦除元素的引用才会失效。

All iterators. 所有迭代器。 All of them. 他们全部。 When you do this: 当你这样做:

mydeque.erase(it++);

It doesn't matter that you post-incremented it , that new iterator is invalidated too. 你后递增it并不重要,新的迭代器也是无效的。 This is precisely why erase() returns : 这正是erase() 返回的原因

Iterator following the last removed element. Iterator跟随最后删除的元素。 If the iterator pos refers to the last element, the end() iterator is returned. 如果迭代器pos引用最后一个元素,则返回end()迭代器。

So that you can do: 这样你就可以做到:

it = mydeque.erase(it); // erase old it, new it is valid

Although even better would be to avoid this source of error entirely by using the erase-remove idiom: 虽然更好的方法是通过使用擦除删除习惯来完全避免这种错误来源:

mydeque.erase(
   std::remove_if(mydeque.begin(), mydeque.end(), [](int i){return i%2 == 0; }),
   mydeque.end()
);

See also this question for more information about iterator invalidation. 有关迭代器失效的更多信息,另请参阅此问题

The code you quoted is for associative containers only ( set , map etc.). 您引用的代码仅用于关联容器setmap等)。

Scott Meyers's Effective STL Item 9 (aptly named "Choose carefully among erasing options") shows how it's done for sequence containers (vector, deque, string) Scott Meyers的有效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;
}

Here, the erase() return value is exactly what we need: it's a valid iterator pointing to the element following the erased element once the erase has been accomplished. 这里, erase()返回值正是我们所需要的:它是一个有效的迭代器,指向擦除完成后擦除元素后面的元素。

So does that mean that in my code, my iterator is being invalidated even though I did a post increment on it before it is deleted? 那么这是否意味着在我的代码中,即使我在删除之前对其进行了后期增量,我的迭代器也会失效?

That's exactly what it means. 这正是它的意思。 Whether that invalidation has any effect on your results or not, that depends on the implementation of the dequeue in your runtime library. 该失效是否会对结果产生任何影响,这取决于运行时库中dequeue的实现。 It might also go well for many cases and then fail all the sudden, like for your 8. 在许多情况下它也可能会很好,然后突然失败,就像你的8。

The post incrementation trick only works for containers, where the erase only invalidates the iterator to the erased element (like for list s and set s). post增量技巧仅适用于容器,其中erase仅使擦除元素的迭代器无效(如list s和set s)。 In those cases the post increment creates an iterator to the next element, and does so before the element is erased. 在这些情况下,post增量会为下一个元素创建一个迭代器,并删除元素之前执行此操作。 That iterator to the next element is therefore immune to the invalidations related to the erase. 因此,对下一个元素的迭代器不受与擦除相关的无效的影响。 In the case of the dequeue , however, the specification says that all iterators are invalidated. 但是,在dequeue的情况下,规范说所有迭代器都是无效的。

C++14 introduces generic erase_if algorithm that works correctly with all types of standard containers. C ++ 14引入了通用的erase_if算法,该算法可以与所有类型的标准容器一起正常工作。

http://en.cppreference.com/w/cpp/experimental/deque/erase_if http://en.cppreference.com/w/cpp/experimental/deque/erase_if

This is equivalent to the last code block provided by @Barry: 这相当于@Barry提供的最后一个代码块:

#include <experimental/deque>
std::experimental::erase_if(mydeque, [](int i){return i%2 == 0; });

It is also better to use this generic algorithm compared to direct erase/remove-if pattern, because if you ever decide to replace your container to std::set , for example, you will not need to update the code that handles removal. 与直接擦除/删除 - 如果模式相比,使用此通用算法也更好,因为如果您决定将容器替换为std::set ,则不需要更新处理删除的代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM