简体   繁体   中英

Iterators invalidation

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

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 usestd::erase_if :

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

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

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.

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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