Iterators invalidation

120 Views Asked by At

Hi I read in C++ primer that adding elements to a vector invalidates the iterators. I don't understand why deleting elements doesn't invalidate them as the following code works

std::vector<int> a = {1,2,3,4,5,6};

auto b = a.begin();

while (b != a.end()){
    
    if (*b%2 != 0)
        a.erase(b);
    else
        b++;
}

NOTE: This code if from C++ primer itself and cpp reference as well

5

There are 5 best solutions below

0
Vlad from Moscow On BEST ANSWER

In general this code snippet

auto b = a.begin();

while (b != a.end()){
    
    if (*b%2 != 0)
        a.erase(b);
    else
        b++;
}

is invalid. It works because the container std::vector satisfies the concept of contiguous ranges. If instead of the vector you will use for example std::list<int> when the iterator b will be invalid.

It would be correctly to write

auto b = a.begin();

while (b != a.end()){
    
    if (*b%2 != 0)
        b = a.erase(b);
    else
        b++;
}
3
Gunther On

Adding elements to a vector may lead to a complete reallocation of the vector. This invalidates all iterators held previously.

If you delete an entry of the vector the erase method returns: a) a iterator to the next valid element b) the end iterator.

But you have to use it. In your case:

b = a.erase(b);
0
gast128 On

Common idiom. From cppreference: (erase) 'Invalidates iterators and references at or after the point of the erase, including the end() iterator.

Others have pointed out it should be written like this:

#include <vector>

std::vector<int> vec = { 1, 2, 3, 4 };

for (auto it = vec.begin(); it != vec.end(); )
{
    if (*it % 2 != 0)
    {
        it = vec.erase(it);
    }
    else
    {
       ++it;
    }
}

Adjust if one prefers 'while' over 'for'. If performance is paramount one can start from the end though this may be less cache friendly.

Edit: code snippet is literally the cppreference link.

0
Mikhail On

Not actually an answer to the question, but I think it is worth mentioning that in modern C++ you should try to avoid iterators by using algorithms and range-based for loops. In this particular case use std::erase_if:

std::vector<int> a = {1,2,3,4,5,6};
std::erase_if(a, [](int x) { return x%2 != 0; });
0
Sergei On

As many pointed out it works by chance. Do not do this in prod. Iterators are designed to be as lightweight as possible so there won't be a flag saying it's invalid. That would be too wasteful.

std::vector iterator is probably implemented as a pointer with some helper functions. Removing one element will shift everything so that same pointer now points to the new element where the old one used to be. It only works because elements are stored in contiguous memory without gaps.