Getting an invalid reference in a range-based for loop [closed] - c++

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
auto& kphist = this->kphist;
for (auto& it : kphist) {
it.second.aging(); // EXC-BAD-ACCESS
if(it.second.age > LAST_DAY){
kphist.erase(it.first);
continue;
}
}
kphist is a private member
Class A{
private:
unordered_map<int, KeyPointHistory> kphist;
}
The debugger shows all item in kphist is valid, how is it possible to have a bad reference inside the for loop. What possibly can go wrong?

From cppreference.com for std::unordered_map::erase(): References and iterators to the erased elements are invalidated. Other iterators and references are not invalidated. Thus, you cannot use std::unordered_map::erase() from within a range for loop (since this will try to increment an invalid iterator).
To avoid incrementing an invalidated iterator, you can simply increment first
and then erase using the original iterator:
for(auto i=map.begin(),end=map.end(); i!=end; ) { // no increment here
auto it=i++; // but here instead
if(must_remove(it))
map.erase(it);
}
In fact, since erase() returns the iterator to the next element, you can avoid the extra iterator it (thanks to Hurkyl to pointing this out in a comment):
for(auto i=map.begin(),end=map.end(); i!=end; ) { // no increment here
if(must_remove(i))
i = map.erase(i); // but here
else
++i; // or here instead
}
No need to make a list of keys of elements to be erased ...
Btw, why don't you use a std::map (rather than an std::unordered_map) as your key is an int (which is easily orderable)? Also, why do you make a reference kphist of a member variable of the same name?

You cannot erase the contents / iterator while you iterate over it and neither should you.
Save the element index in a different container and when you're done loop through that and erase the elements you got.
What possibly can go wrong?
Everything!

You can erase from an unordered_map by directly passing the iterator to the item to be erased. When you do so, erase() returns the subsequent iterator, so you can do something like this:
for (auto pos = kphist.begin(); pos != kphist.end(); ) {
it.second.aging();
if(it.second.age > LAST_DAY)
pos = kphist.erase(it);
else
++pos;
}
As a bonus, this will probably be a bit faster than passing the key to be erased--since you're providing the iterator, it can get to the item to be erased directly rather than re-hashing the key to find the position you already knew.

Related

Deleting/Removing a rectangle from a vector C++ (SDL2) [duplicate]

Very simple: I have the following code and the method erase is not working. I do not see any problem there because if I go to http://www.cplusplus.com/reference/list/list/erase/ , syntax is: iterator erase (iterator position);
list<pair<string,int>> l0 { { "name1", 20 }, { "name2", 30 }, { "name3", 40 } };
for( auto &it : l0 )
l0 . erase( it );
May there be a problem that there is a list of pair<string,int> and not a list of a basic data types?
EDIT: The problem is that the code is not compilable.
The range-for iterates through a container by giving you access to the
elements in the container, and not an iterator to an element.
So in for( auto &it : l0 ), it isn't an iterator to a pair but
a reference to a pair. This is why your code doesn't compile
This being said, as πάνταῥεῖ pointed out when he initially closed this as a duplicate of
Keeping a valid vector::iterator after erase(), even if your code would
compile it wouldn't work because of the invalidation of the iterator following the erase:
Iterators, pointers and references referring to elements removed by the function are invalidated.
All other iterators, pointers and references keep their validity.
Workaround
You shall not use the range-for, but the traditional for, and iterating using the return value of erase() :
for (auto it=l0.begin(); it!=l0.end(); )
it = l0.erase(it); // to avoid incrementing an invalidated iterator
Live demo
The answer above given by Christophe is perfect and that helped me as well. I came up with something like this (I had some other requirement). I hope this can help someone for sure.
You can use remove_if command for achieving something similar.
//Suppose user wants to remove the entry with fits value as "name1"
l0.remove_if([key](auto it) {
return it.first == "name1";
});

C++ loop through map while erasing [duplicate]

This question already has answers here:
What happens if you call erase() on a map element while iterating from begin to end?
(3 answers)
Closed 9 years ago.
To loop through a map in c++ we do sth like this
map<string,int> mymap;
map<string,int>::iterator it= mymap.begin();
while(it!=mymap.end()) {
//code here
it++;
}
What if in the "code here" part i have an if statement that if evaluated to true, it erases one element from the map? How should my code change so that it still loops through all mymap elements in order?
http://en.cppreference.com/w/cpp/container/map/erase :
References and iterators to the erased elements are invalidated. Other
references and iterators are not affected.
(So make sure you increment and save a "next" iterator before you erase.
Edit: In fact since C++11, erase returns the next iterator anyway, so you may use that.)
you may want to reassign your iterator when you erase an element, as it wont be valid otherwise...
it = mymap.erase(...)
To avoid using the iterator after invalidating it when erasing, the loop body should be like this:
if (should_erase) {
it = my_map.erase(it); // C++11: returns the next iterator
my_map.erase(it++); // Historic C++: no helpful return value
} else {
++it;
}

Post increment on set iterator [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
iterator validity ,after erase() call in std::set
When I iterate over a set and want to delete certain items the iterators are changed. This results in segfaults as the iteration fails after deletion. How can I overcome this problem?
std::set< std::pair<double,unsigned>, comparisonFunction> candidates;'
[...]
for( auto it = candidates.begin(); it != candidates.end(); ++it)
{
[...]
if ( some constraint satisfied)
{
candidates.erase(it);
}
}
I encounter a segfault when I use this code. My guess is that this is either due to the corrupted iterators or due to the fact, that the element to be deleted is the last element in some cases. Does a post increment on the iterator overcome this problem? Like this:
candidate.erase(it++);
Use the return value of erase:
it = candidates.erase(it);
Note that you must not increment it if you erase an element, otherwise your iterator could be invalidated.
for( auto it = candidates.begin(); it != candidates.end();)
{
if ( some constraint satisfied)
{
it = candidates.erase(it);
}
else
++it;
}
Also note that this wasn't possible in C++03, since erase didn't return any iterator. However, since you're using C++11 it shouldn't be a problem.
References
std::set::erase

update map value

I have a map like this:
map<prmNode,vector<prmEdge>,prmNodeComparator> nodo2archi;
When I have to update the value (vector), I take the key and his value, I update the vector of values, I erase the old key and value then I insert the key and the new vector. The code is this:
bool prmPlanner::insert_edgemap(int from,int to) {
prmEdge e;
e.setFrom(from);
e.setTo(to);
map<prmNode,vector<prmEdge> >::iterator it;
for (it=nodo2archi.begin(); it!=nodo2archi.end(); it++){
vector<prmEdge> appo;
prmNode n;
n=(*it).first;
int indice=n.getIndex();
if (indice==f || indice==t){
appo.clear();
vector<prmEdge> incArchi;
incArchi=(*it).second;
appo=(incArchi);
appo.push_back(e);
nodo2archi.erase(it);
nodo2archi.insert(make_pair(n,appo) );
}
}
return true;
}
The problem is that for the first 40-50 iterations everything go weel and the map is updated well, while with more iterations it goes sometimes in segmentation fault, sometimes in an infinite idle. I don't know why. Somebody can help me please??
Thank you very much.
You are iterating through nodo2archi and at the sametime changing its size by doing nodo2archi.erase(it); and nodo2archi.insert(make_pair(n,appo) );. If you do that your iterator may become invalid and your it++ might crash.
Are you simply trying to append data to some of the mapped vectors? In this case you don't need to erase and insert anything:
for (MapType::iterator it = map.begin(); it != map.end(); ++it) {
if (some_condition) {
it->second.push_back(some_value);
}
}
The problem is that after erasing the iterator it you are trying to perform operations on it (increment) which is Undefined Behavior. Some of the answers state that modifying the container while you are iterating over it is UB, which is not true, but you must know when your iterators become invalidated.
For sequence containers, the erase operation will return a new valid iterator into the next element in the container, so this would be a correct and idiomatic way of erasing from such a container:
for ( SequenceContainer::iterator it = c.begin(); it != c.end(); )
// note: no iterator increment here
// note: no caching of the end iterator
{
if ( condition(*it) ) {
it = c.erase(it);
} else {
++it;
}
}
But sadly enough, in the current standard, associative containers erase does not return an iterator (this is fixed in the new standard draft), so you must manually fake it
for ( AssociativeContainer::iterator it = c.begin(); it != c.end(); )
// again, no increment in the loop and no caching of the end iterator
{
if ( condition(*it) ) {
AssociativeContainer::iterator del = it++; // increment while still valid
c.erase(del); // erase previous position
} else {
++it;
}
}
And even more sadly, the second approach, correct for associative containers, is not valid for some sequence containers (std::vector in particular), so there is no single solution for the problem and you must know what you are iterating over. At least until the next standard is published and compilers catch up.
Yo do modify collection while iterating over it.
You are erasing nodes while iterating through your map. This is asking for trouble :)
You must not modify a collection itself while iterating over it. C++ will allow it, but it still results in undefined behavior. Other languages like Java have fail-fast iterators that immediately break when the collection has been modified.

How to filter items from a std::map? [duplicate]

This question already has answers here:
What happens if you call erase() on a map element while iterating from begin to end?
(3 answers)
Closed 6 years ago.
I have roughly the following code. Could this be made nicer or more efficient? Perhaps using std::remove_if? Can you remove items from the map while traversing it? Can we avoid using the temporary map?
typedef std::map<Action, What> Actions;
static Actions _actions;
bool expired(const Actions::value_type &action)
{
return <something>;
}
void bar(const Actions::value_type &action)
{
// do some stuff
}
void foo()
{
// loop the actions finding expired items
Actions actions;
BOOST_FOREACH(Actions::value_type &action, _actions)
{
if (expired(action))
bar(action);
else
actions[action.first]=action.second;
}
}
actions.swap(_actions);
}
A variation of Mark Ransom algorithm but without the need for a temporary.
for(Actions::iterator it = _actions.begin();it != _actions.end();)
{
if (expired(*it))
{
bar(*it);
_actions.erase(it++); // Note the post increment here.
// This increments 'it' and returns a copy of
// the original 'it' to be used by erase()
}
else
{
++it; // Use Pre-Increment here as it is more effecient
// Because no copy of it is required.
}
}
You could use erase(), but I don't know how BOOST_FOREACH will handle the invalidated iterator. The documentation for map::erase states that only the erased iterator will be invalidated, the others should be OK. Here's how I would restructure the inner loop:
Actions::iterator it = _actions.begin();
while (it != _actions.end())
{
if (expired(*it))
{
bar(*it);
Actions::iterator toerase = it;
++it;
_actions.erase(toerase);
}
else
++it;
}
Something that no one ever seems to know is that erase returns a new, guaranteed-to-be-valid iterator, when used on any container.
Actions::iterator it = _actions.begin();
while (it != _actions.end())
{
if (expired(*it))
{
bar(*it);
it = _actions::erase(it);
}
else
++it;
}
Storing actions.end() is probably not a good plan in this case since iterator stability is not guaranteed, I believe.
If the idea is to remove expired items, why not use map::erase? This way you only have to remove elements you don't need anymore, not rebuild an entire copy with all the elements that you want to keep.
The way you would do this is to save off the iterators pointing to the elements you want to erase, then erase them all after the iteration is over.
Or, you can save off the element that you visited, move to the next element, and then erase the temporary. The loop bounds get messed up in your case though, so you have to fine tune the iteration yourself.
Depending on how expired() is implemented, there may be other better ways. For example if you are keeping track of a timestamp as the key to the map (as expired() implies?), you can do upper_bound on the current timestamp, and all elements in the range [ begin(), upper_bound() ) need to be processed and erased.