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";
});
Related
I have an std::map and an std::unordered_set with the same key.
I want to remove all keys from the set that do not exist in the map.
My idea was to do something like the following:
#include <map>
#include <unordered_set>
int main()
{
std::map<uint64_t, std::string> myMap = {
{1, "foo"},
{2, "bar"},
{3, "morefoo"},
{4, "morebar"}
};
std::unordered_set<uint64_t> mySet = { 1, 2, 3, 4, 123 };
for (const auto key : mySet)
{
const auto mapIterator = myMap.find(key);
if (myMap.end() == mapIterator)
mySet.erase(key);
}
return 0;
}
And then invoke it on a timer callback every few seconds, however, the snippet above throws an assert when trying to delete the key 123 from mySet, stating:
List iterator is not incrementable.
The thing is, even if it didn't throw an exception I feel like this idea is far from elegant/optimal. I'm wondering if there is a better way to approach this problem?
Thanks.
As stated in answers to this question How to remove from a map while iterating it? you cannot erase elements in container while iterating over it in a for range loop, so your code should use iterator. As such answer already provided I would not duplicate it.
For efficiency you have a quite wrong approach - you are doing lookup in std::map while iterating std::unordered_set, which should be opposite as std::unorederd_set provides faster lookup time (otherwise there is not point for you use it instead of std::set). One of possible approach is to create a copy:
auto copySet = mySet;
for( const auto &p : myMap )
copySet.erase( p.first );
for( const auto v : copySet )
mySet.erase( v );
which could be more efficient, but that depends on your data. Better approach to choose proper data types for your containers.
Note: by wrong approach I mean efficiency only for this particular situation presented in your question, but this seem to be a part of a larger program and this solution can be right for it as there could be more important cases when this data structure works well.
As stated in the comments
for (const auto key : mySet)
{
const auto mapIterator = myMap.find(key);
if (myMap.end() == mapIterator)
mySet.erase(key);
}
will have undefined behavior. When you erase element 123 from mySet you invalidate the iterator that the range based for loop is using. incrementing that iterator is not allowed after you do that. What you can do is switch to a regular for loop so you can control when the iterator is incremented like
for (auto it = mySet.begin(); it != mySet.end();)
{
if (myMap.find(*it) == myMap.end())
it = mySet.erase(it);
else
++it;
}
and now you always have a valid iterator as erase will return the next valid iterator.
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.
I am looping through a vector with a loop such as for(int i = 0; i < vec.size(); i++). Within this loop, I check a condition on the element at that vector index, and if a certain condition is true, I want to delete that element.
How do I delete a vector element while looping over it without crashing?
The idiomatic way to remove all elements from an STL container which satisfy a given predicate is to use the remove-erase idiom. The idea is to move the predicate (that's the function which yields true or false for some element) into a given function, say pred and then:
static bool pred( const std::string &s ) {
// ...
}
std::vector<std::string> v;
v.erase( std::remove_if( v.begin(), v.end(), pred ), v.end() );
If you insist on using indices, you should not increment the index for every element, but only for those which didn't get removed:
std::vector<std::string>::size_type i = 0;
while ( i < v.size() ) {
if ( shouldBeRemoved( v[i] ) ) {
v.erase( v.begin() + i );
} else {
++i;
}
}
However, this is not only more code and less idiomatic (read: C++ programmers actually have to look at the code whereas the 'erase & remove' idiom immediately gives some idea what's going on), but also much less efficient because vectors store their elements in one contiguous block of memory, so erasing on positions other than the vector end also moves all the elements after the segment erased to their new positions.
If you cannot use remove/erase (e.g. because you don't want to use lambdas or write a predicate), use the standard idiom for sequence container element removal:
for (auto it = v.cbegin(); it != v.cend() /* not hoisted */; /* no increment */)
{
if (delete_condition)
{
it = v.erase(it);
}
else
{
++it;
}
}
If possible, though, prefer remove/erase:
#include <algorithm>
v.erase(std::remove_if(v.begin(), v.end(),
[](T const & x) -> bool { /* decide */ }),
v.end());
Use the Erase-Remove Idiom, using remove_if with a predicate to specify your condition.
if(vector_name.empty() == false) {
for(int i = vector_name.size() - 1; i >= 0; i--)
{
if(condition)
vector_name.erase(vector_name.at(i));
}
}
This works for me. And Don't need to think about indexes have already erased.
Iterate over the vector backwards. That way, you don't nuke the ability to get to the elements you haven't visited yet.
I realize you are asking specifically about removing from vector, but just wanted to point out that it is costly to remove items from a std::vector since all items after the removed item must be copied to new location. If you are going to remove items from the container you should use a std::list. The std::list::erase(item) method even returns the iterator pointing to the value after the one just erased, so it's easy to use in a for or while loop. Nice thing too with std::list is that iterators pointing to non-erased items remain valid throughout list existence. See for instance the docs at cplusplus.com.
That said, if you have no choice, a trick that can work is simply to create a new empty vector and add items to it from the first vector, then use std::swap(oldVec, newVec), which is very efficient (no copy, just changes internal pointers).
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.
I was wondering if something like this is safe...
// Iterating through a <list>
while ( iter != seq.end()) {
if ( test ) {
iter = seq.erase( iter );
} else {
++iter;
}
I know that iterating through a vector in this way would invalidate the iterator, but would the same thing occur in a list? I assume not since a list is sequential through pointers rather than being "next" to each other in memory, but any reassurance would be helpful.
This is just fine because the erase method returns a new valid iterator.
Yes -- std::list::erase(): "Invalidates only the iterators and references to the erased elements."
That said, you probably shouldn't do this at all -- you seem to be trying to imitate std::remove_if().
The standard defines erase behaviour for every STL container. For std::list only iterators to the erased elements are invalidated. The return value of erase needn't be a dereferencable one, though (it could be list.end()).
Therefore, to erase all elements in a list the following is absolutely valid:
.. it = l.begin();
while(it != l.end()) {
it = l.erase(it);
}
BUT beware of something like this (dangerous pitfall):
for (.. it = l.begin; it != l.end(); ++it) {
it = l.erase(it);
}
If it is l.end(), it is incremented twice (second time by the loop head). Baamm.
Yes, this is the standard way to do that. See Effective STL, Item 9 (p. 46).
Yes, this is totally safe. The erase() function returns an iterator to the element succeeding the one which was erased. Had you not reassigned the result of erase() to iter, you'd have trouble.
As others have explained, your code does not invalidate the iterator used in the function. However, it does invalidate other iterators if the collection is a vector, but not if the collection is a list.
As others have mentioned, yes, it will work. But I'd recommend using list::remove_if instead, as it's more expressive.