I don't understand, why it is runtime error? Erase set element while iterating.
set<int> sset;
sset.insert(3);
sset.insert(5);
sset.insert(6);
for(auto s: sset){
sset.erase(s);
}
I don't understand, why it is runtime error? Erase set element while iterating.
set<int> sset;
sset.insert(3);
sset.insert(5);
sset.insert(6);
for(auto s: sset){
sset.erase(s);
}
Sadly, erasing elements from sset
will invalidate the implicit iteration over the container; that's used by the nice syntactic sugar auto s : sset
.
That's undefined behaviour.
So just to explain further,
What you effectively have written is:
for (set<int>::const_iterator i=sset.begin(), e=sset.end(); i != e; i++)
{
auto s = *i;
sset.erase(s);
}
So the issue is that on doing the erase, the internal iterator i
becomes invalidated. This is a general pain with trying to sequentially delete the content of many of the containers.
The following more traditional sequential delete code is also bad for the same reason, but perhaps more obviously:
for (set<int>::iterator i=sset.begin(), e=sset.end(); i != e; i++)
{
sset.erase(i);
}
Fixes:
Generally, it is simpler to rely on context swap destruction of the whole container, when you can:
C++98: SsetType().swap(sset);
C++11: sset = decltype<sset>();
And you could just do:
sset.erase(sset.begin(), sset.end());
Another way to fix this is to just keep deleting the begin()
until the set is empty()
But the problem with all of these is you can't easily extend them to conditionally erase members of a set you are iterating through. Yes, there are helpers for conditional erase as well, and they can be used with lambdas, so they can carry state, but they generally tend to be as hard to use as rolling your own loop.
Since c++11, set::erase(iterator) returns a new iterator which is safe to continue iterating with, so you can write:
for (set<int>::iterator i=sset.begin(), e=sset.end(); i != e; )
{
i = sset.erase(i);
}
If you were performing some conditional test, then:
for (set<int>::iterator i=sset.begin(), e=sset.end(); i != e; )
{
if ( ... condition ... )
i = sset.erase(i);
else
i++;
}
before, in c++98, you would have written something like:
for (set<int>::iterator i=sset.begin(), e=sset.end(); i != e; )
{
auto j = i;
j++;
if ( ... condition ... )
i = sset.erase(i);
i = j;
}
As an exercise, you can roll the use of j
into the for
statement. getting the initial j++ in C98 is tricky, though!