C++ std::list: Erasing / removing elements while iterating [duplicate] - c++

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Can you remove elements from a std::list while iterating through it?
I have a loop in a function, that iterates over an std::list from begin to end.
In each cycle, I perform some checks and maybe do a few manipulations on the current list entry, and in some cases I want to remove it from the list.
Now, as expected my iterator gets invalidated.
Is there any way to work around this, to remove elements from a list while iterating over it?

Catch the return value of erase and use it as your iterator. The return value is an iterator to the next valid location after the erasure.
if(ShouldErase)
{
iter = list.erase(iter);
}
else
{
++iter;
}
Reference
Excerpt:
Return value
A bidirectional iterator pointing to the new location of the element that followed the last element erased by the function call, which is the list end if the operation erased the last element in the sequence.

Use postfix increment.
list.erase(it++);
it is increased, so it no longer refers to the erased element, then the previous value of it is given to list.erase. Make sure that you either do list.erase(it++) or ++it in your loop - doing both will skip elements and potentially increment past end of the list.

Have you considered using the list::remove_if algorithm?

Related

C++ loop on map not detecting change of map`s end

I am having a problem while looping thru a map (std::map).
Inside my loop, there is a call to a function which sometimes (not always) erases elements of this same map. After this function is used, there is some code which is using some of this map information as input.
I am having no problems after this function erases any elements, except on the unique case that the last element of the map is erased.
My loop semms not to understand that the last element of the map is not the same as when it started to operate, and will try to operate on elements which doesnt exist, creating a crash.
It seems to me that the myMap.end() call on the loop description is not able to update itself with the new end() of the map.
The relevant part of the code is listed below:
for(std::map<int, ConnectionInfo>::iterator kv = myMap.begin(); kv != myMap.end(); ++kv) {
int thisConnectionID=kv->first; //This is where I get garbage when the loop enters when it shouldnt;
ConnectionInfo currentConnectionInfo=kv->second; //This is where I get garbage when the loop enters when it shouldnt;
status=eraseSomeMapElementsIfNecessary(thisConnectionID,currentConnectionInfo.DownPacket); //this function might erase elements on myMap. This generates no problems afterwards, except when the end element of myMap is erased
... //Next parts of the code make no further usage of myMaps, so I just hid it not to pollute the code
}
Is my interpretation that the kv != myMap.end() is not being able to understand that the inner loop is changing (erasing) the last element (end) of myMap?
In this case, how can I fix this issue?
Or is my interpretation wrong and the solution has nothing to do with what I stated before?
Thanks for your help!
The usual idiom when iterating a map with possibly deleting element is:
for(auto it = map.begin(); it != map.end(); ) {
if ( *it == /*is to delete*/ ) {
it = map.erase(it);
}
else
++it;
}
if your eraseSomeMapElementsIfNecessary might erase some random values in map being iterated then this will for sure cause problems. If element to which it is referencing was erased, it becomes invalid, then incrementing it with ++it is also invalid.
The problem is actually only with the it iterator, if eraseSomeMapElementsIfNecessary erases it and then you use it - you have Undefined Behaviour (UB). So the solution is to pass current iterator to eraseSomeMapElementsIfNecessary, and return from it the next one to iterate:
it = eraseSomeMapElementsIfNecessary(it);
the body of the for loop from my example should be inside your eraseSomeMapElementsIfNecessary function. At least this is one solution.
I am having no problems after this function erases any elements, except on the unique case that the last element of the map is erased.
Erasing an element in any container invalidates the iterator to it. After that you increment the invalidated iterator.
You should increment the iterator before you delete the element pointed by it.
If you do not know what elements that function inside the loop erases assume that all iterators are invalidated.
Maybe these 2 links will help:
How can I delete elements of a std::map with an iterator?
https://stackoverflow.com/a/8234813/3464942
Basically, what it all boils down to, is that you must update the iterator before it becomes invalid.
You have to preserve the next iterator before erasing the current one; since the current one will be invalid after deleting the element.
auto nextit = it+1;
map.erase(it);
it = nextit;

Erasing item in a for(-each) auto loop

Is there a way to erase specific elements when using a auto variable in a for loop like this?
for(auto a: m_Connections)
{
if(something)
{
//Erase this element
}
}
I know I can either do say
for(auto it=m_map.begin() ...
or
for(map<int,int>::iterator it=m_map.begin() ...
and manually increment the iterator (and erase) but if I could do it with less lines of code I'd be happier.
Thanks!
You can't. A range-based loop makes a simple iteration over a range simpler, but doesn't support anything that invalidates either the range, or the iterator it uses. Of course, even if that were supported, you couldn't efficiently erase an element without access to the iterator.
You'll need an old-school loop, along the lines of
for (auto it = container.begin(); it != container.end();) {
if (something) {
it = container.erase(it);
} else {
++it;
}
}
or a combination of container.erase() and std::remove_if, if you like that sort of thing.
No, there isn't. Range based for loop is used to access each element of a container once.
Every time an element is removed from the container, iterators at or after the erased element are no longer valid (and given the implementation of the range-based-for this is a problem).
You should use the normal for loop (or a while) if you need to modify the container as you go along.
If you want to erase elements for which a predicate returns true, a good way is:
m_Connections.erase(
std::remove_if(m_Connections.begin(),
m_Connections.end(),
[](Type elem) { return predicate(elem); }),
m_Connections.end());
std::remove_if doesn't mix iteration logic with the predicate.
You need the iterator if you want to erase an element from a container.
And you can't get the iterator from the element itself -- and even if you could, for instance with vector, the iterator that range-based for internally uses would be invalidated in the next step causing undefined behavior.
So the answer is: No, in its classic usage you can't. range-based for was solely designed for convenient iteration of all elements in a range.
push all elements into array and then do pop operation to remove the item

Does inserting/erasing an element from a std::map modify the iteration sequence?

Say I have the following code:
typedef std::map< int, std::string >::iterator Iterator;
Iterator iter = myMap.begin();
while (iter != myMap.end())
{
Iterator current = iter;
++iter;
maybeDeleteElement( current ) // may call erase.
}
Given that std::map is implemented as a red-black tree, is it guaranteed that every element in the map will be visited exactly once? Or will modifying the map cause the tree to rebalance, and thus the iteration sequence to change?
Note: This is not a question about whether or not any iterators will be invalidated. But an iterator remaining valid does not necessarily mean that incrementing it will give you the same next element that it did before.
In a std::map the elements will be visited in order.
If you store an iterator that refers to an element that is not deleted, and hence not invalidated, the iterator will still refer to that same element. (If it was the end iterator, it remains the end iterator, as it is not invalidated).
When you advance that iterator, it will advance to the next element in order after the element you refer to.
For your particular example, yes, every element will be visited exactly once, because all deletion of elements was elements that are before the current iterator state of your loop.
If you insert elements ahead of whatever iterator you are using to iterate, then you'll eventually reach them as you iterate forward with your iterator. If you delete elements ahead of whatever iterator you are using to iterate, then they are no longer part of the future elements you'll reach if you iterate with that iterator.
If you insert or delete elements that are before the current location of the iterator, unless you start calling -- or similar functions, your current iteration will continue without noticing that they went away.
This is because ++ on a valid iterator in an ordered container is guaranteed to return the next element in the order, and operations on other iterators that do not invalidate an iterator don't change that iterator's invariants (like what element they refer to).
Yes, inserting/erasing can modify the iteration sequence. It does not happen in your example, as you erase iterators you've already passed by, but if you erase/insert elements that are positioned ahead of your current iterator, then it will modify the rest of the sequence.
Here is a short code which displays such behavior:
int main (){
map<int,int> mapa;
for(int i = 0; i < 5; ++i) mapa[i] = i;
bool add = false;
for(auto it = mapa.begin(); it != mapa.end(); ++it){
int x = it->second;
printf("%d\n", x);
if(add) mapa.erase(x+1);
add = !add;
}
return 0;
}
The example above will print 0 1 3 4 (instead of 0 1 2 3 4). Additionally, if you erase the current iterator, its reference to the next element will be invalidated and your program will crash at the next iteration.
Alternatively, you can also test the insertion, by substituting the if(add) above with:
if(add) mapa[x+5] = x+5;
else mapa[x-20] = x-20;
The example will print the extra elements {6, 8, 11, 16}, and not print the negative ones, since those are being inserted in a position prior to your current one.
Erasing an element from a map does not invalidate iterators, so I would expect it to continue to iterate properly.
See https://stackoverflow.com/a/6438087/5987
Yes, the iteration sequence changes. This is due to ยง23.2.4.1/10 and /11:
(p10) The fundamental property of iterators of associative containers is that they iterate through the containers in the non-descending order of keys where non-descending is defined by the comparison that was used to construct them. For any two dereferenceable iterators i and j such that distance from i to j is positive,
value_comp(*j, *i) == false
(p11)
For associative containers with unique keys the stronger condition holds,
value_comp(*i, *j) != false.
If, after an insert, the new element were added at the beginning of the iteration sequence regardless of the ordering of elements (so as to not to modify the sequence ahead of any existing iterator position), the above requirement would be violated.
In spite of your note, for the erase case this question is exactly about iterator invalidation, because if no iterator is invalidated, the order necessarily remains the same. This is because map is a sorted container, so no matter what internal representation changes may happen, the iteration order has to remain exactly the same.
To address specifically your example, it will traverse each element exactly once, because you save off the iterator to check and increment your traversal iterator.
In the case of insertion, if you insert before the current point of iteration that element won't be visited. Inserting after the current iterator will result in the new item being traversed.

Vector.erase(Iterator) causes bad memory access

I am trying to do a Z-Index reordering of videoObjects stored in a vector. The plan is to identify the videoObject which is going to be put on the first position of the vector, erase it and then insert it at the first position. Unfortunately the erase() function always causes bad memory access.
Here is my code:
testApp.h:
vector<videoObject> videoObjects;
vector<videoObject>::iterator itVid;
testApp.cpp:
// Get the videoObject which relates to the user event
for(itVid = videoObjects.begin(); itVid != videoObjects.end(); ++itVid) {
if(videoObjects.at(itVid - videoObjects.begin()).isInside(ofPoint(tcur.getX(), tcur.getY()))) {
videoObjects.erase(itVid);
}
}
This should be so simple but I just don't see where I'm taking the wrong turn.
You should do
itVid = videoObjects.erase(itVid);
Quote from cplusplus.com:
[vector::erase] invalidates all iterator and references to elements after position or first.
Return value: A random access iterator pointing to the new location of the element that followed the last element erased by the function call, which is the vector end if the operation erased the last element in the sequence.
Update: the way you access the current element inside your condition looks rather strange. Also one must avoid incrementing the iterator after erase, as this would skip an element and may cause out-of-bounds errors. Try this:
for(itVid = videoObjects.begin(); itVid != videoObjects.end(); ){
if(itVid->isInside(ofPoint(tcur.getX(), tcur.getY()))){
itVid = videoObjects.erase(itVid);
} else {
++itVid;
}
}
Beware, erasing elements one by one from a vector has quadratic complexity. STL to the rescue!
#include <algorithm>
#include <functional>
videoObjects.erase(
std::remove_if(
std::bind2nd(
std::mem_fun_ref(&videoObject::isInside),
ofPoint(tcur.getX(), tcur.getY())
),
),
videoObjects.end()
);
You cannot delete while iterating over the list because the iterator gets invalid. You should use the return iterator of Erase to set it to your current iterator.
erase function returns the next valid iterator.
You would have to make a while loop and do something like
iterator = erase(...)
with corresponding checks.

delete a specific entry in the map,but the iterator must point to the next element after the deletion [duplicate]

This question already has answers here:
Closed 14 years ago.
Duplicate:
What happens if you call erase on a map element while iterating from begin to end
How to filter items from a stdmap
I have a map map1<string,vector<string>> i have a iterator for this map "itr".
i want to delete the entry from this map which is pointed by "itr".
i can use the function map1.erase(itr); after this line the iterator "itr" becomes invalid.
as per my requirement in my project,the iterator must point to the next element. can any body help me regerding this
thans in advance:)
santhosh
You can post-increment the iterator while passing it as argument to erase:
myMap.erase(itr++)
This way, the element that was pointed by itr before the erase is deleted, and the iterator is incremented to point to the next element in the map. If you're doing this in a loop, beware not to increment the iterator twice.
See also this answer from a similar question, or what has been responded to this question.
map<...>::iterator tmp(iter++);
map1.erase(tmp);
Simple Answer:
map.erase(iter++); // Post increment. Increments iterator,
// returns previous value for use in erase method
Asked and answered before:
What happens if you call erase on a map element while iterating from begin to end
How to filter items from a stdmap
#include <boost/next_prior.hpp>
map<string,vector<string> >::iterator next = boost::next(itr);
map1.erase(iter);
iter = next;