4

Why does the following code crash? And what should I do when I am iterating via reverse iterator. How do I erase individual elements then?

deque q;
q.push_back(4);
q.push_back(41);
q.push_back(14);

for (auto it = q.begin(); it != q.end();) {
    auto current = it;
    q.erase(current);
    it++; 
}
moooeeeep
  • 31,622
  • 22
  • 98
  • 187
Ram
  • 389
  • 4
  • 13
  • Have you tried switching your standard library into diagnostic or debug mode? It would probably tell you that you are using an invalid iterator. – Ulrich Eckhardt May 27 '16 at 06:57

4 Answers4

4
  1. Why does the following code crash ? How do I erase individual elements then ?

std::deque::erase invalidates iterators.

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.

The past-the-end iterator is also invalidated unless the erased elements are at the beginning of the container and the last element is not erased.

In your code, the iterators to the element to be erased (i.e. it and current) will become invalid after q.erase(current), then it++ will lead to UB.

You could make use of the return value of std::deque::erase

Iterator following the last removed element. If the iterator pos refers to the last element, the end() iterator is returned.

for (auto it = q.begin(); it!=q.end(); )
{
    it = q.erase(it);
}
  1. And what should I do if I am iterating via reverse iterator.

Because std::deque::erase doesn't accept reverse_iterator as parameters, you need to use base() to convert it to normal iterator (pay attention to the position conversion). Such as

for (auto it = q.rbegin(); it!=q.rend(); )
{
    it = std::make_reverse_iterator(q.erase((++it).base()));
}
Community
  • 1
  • 1
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • Fantastic answer! That helped a lot. So if at all I need to store a position in the deque , that I wish to access later, how can I do that ? In between the store and access, some elements may also be erased. – Ram May 27 '16 at 07:44
  • @Ram You have to handle it by yourself, i.e. insuring iterators still valid when use them. – songyuanyao May 27 '16 at 07:50
2

As per C++11 23.3.3.4 deque modifiers /4, deque iterators become invalid if you delete certain elements.

An erase operation that erases the last element of a deque invalidates only the past-the-end iterator and all iterators and references to the erased elements.

An erase operation that erases the first element of a deque but not the last element invalidates only the erased elements.

An erase operation that erases neither the first element nor the last element of a deque invalidates the past-the-end iterator and all iterators and references to all the elements of the deque.

In your case, you're usually only ever erasing the first element so it will only invalidate that element. That means the it++ is invalid and you should instead use something like:

it = q.erase(it);

inside the loop, since the erase call itself returns an "adjusted" iterator. This will also work when removing the last element.

However, since your code is totally clearing the list (assuming it's not a cut down version of something which needs to process each element), you can ditch the loop altogether and just use:

q.clear();
Community
  • 1
  • 1
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Fantastic answer! That helped a lot. So if at all I need to store a position in the deque , that I wish to access later, how can I do that ? In between the store and access, some elements may also be erased. – Ram May 27 '16 at 07:46
  • @Ram, saving an iterator "position" won't help if you modify the collection between saving and using - the position will be invalidated. If the items in the collection are somehow unique, you can store the item value itself rather than the position then later iterate over the list looking for that item and deleting it. – paxdiablo May 27 '16 at 07:48
1

As the other answerers have already pointed out, erasing elements from the queue will invalidate the iterators you are using to iterate its elements. Thus it fails.

But I assume that you don't intend to erase all elements in the queue, in which case you probably would have rather used:

q.erase(q.begin(), q.end());

or

q.clear();

Therefore, I'd like to suggest using another technique, that can be used to delete items matching certain criteria from a queue: the erase-remove idiom.

Here, the functions std::remove(...) and std::remove_if(...) are used to move the items to be deleted (matching certain criteria) to the end of the container. The range-based version of q.erase(...) is then used to delete the items.

Here's an example:

#include <deque>
#include <algorithm>
#include <iostream>

// predicate function for removal of elements
bool greater_three(int x) {
    return x > 3;
}

int main() {
    std::deque<int> q = {1,2,3,4,5};
    for (auto i : q) std::cout << i << " "; std::cout << "\n";
    // delete all items with value 3
    q.erase(std::remove(q.begin(), q.end(), 3), q.end()); 
    for (auto i : q) std::cout << i << " "; std::cout << "\n";
    // delete all items with value > 3
    q.erase(std::remove_if(q.begin(), q.end(), greater_three), q.end()); 
    for (auto i : q) std::cout << i << " "; std::cout << "\n";
}

The output is:

$ g++ test.cc -std=c++11 && ./a.out
1 2 3 4 5 
1 2 4 5 
1 2 

For reference:

moooeeeep
  • 31,622
  • 22
  • 98
  • 187
0

q clearly doesn't support removing elements while iterating through them.

kcraigie
  • 1,252
  • 6
  • 13