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.
Related
I have a boost::multi_index with a hashed_non_unique view. What I would like to accomplish is, given a key in that view, use
pair<myIter, myIter> iterRange = myView.equal_range(key);
for (myIter iter = iterRange.first; iter != iterRange.second; ++iter) {
// ...
}
to find all elements associated with that key. Then, run these elements through a filter
bool filter(Element e) { /* some filtering logic*/ }
and modify the filtered results with a modifier
void modifier(Element e) { /* modify the elements with e.g. myView.modify() */ }
However, simply putting these pieces together doesn't work since modifying the elements results in reordering of the multi_index, which renders my iterRange invalid.
What would be the correct way to do this? Thanks!
Some comments to your proposed solution:
BMIter is not special as you seem to imply, but merely the iterator associated to the first index of the container. Note that this will be the same as myIter when myView happens to be this first index.
Nevertheless, iterators of hashed indices are not invalidated by insertions or modifications, so you're safe. In fact, you could have defined iters as a vector<myIter> and store the iterators directly without any further conversion --you're still achieving the intended effect of not being affected by potential reorderings after modification.
Even though what you're doing is perfectly fine, if you're into squeezing some additional performance note that modification of elements in a hashed index does not change the underlying ordering when the key remains equivalent, so the only way a reordering can possibly affect you when traversing a range of equivalent keys is when a modified element jumps directly after the range (i.e, just before iterRange.second). With this mind, you can spare the iters trick as follows:
for (myIter iter = iterRange.first; iter != iterRange.second; ) {
auto nextIter = std::next(iter);
if (filter(*iter)) {
myView.modify(iter, modifier);
if (nextIter != iterRange.second && std::next(iter) == iterRange.second)
iterRange.second = iter;
}
iter = nextIter;
}
I think I've found the answer myself. Instead of simply modifying the elements inside the for loop, we need to cache the elements first and modify them later to avoid altering the ordering. The trick here is, instead of caching the iterators to this specific view, cache instead iterators to the elements themselves, i.e.
vector<BMIIter> iters;
for (myIter iter = iterRange.first; iter != iterRange.second; ++iter) {
if (filter(*iter)) {
iters.push_back(myBMI.iterator_to(*iter));
}
}
for (auto iter : iters) {
myBMI.modify(iter, modifier);
}
Note that BMIIter and myIter are different iterator types - the former is an iterator to the element itself, while the latter is iterator specific to myView. Modifying elements in the multi_index invalidates the latter, but the former still holds valid even after reordering happened.
Sorry, I know there are questions where people ask how to remove duplicates, but I thought this warranted a new thread since there is something weird coming out of my compiler.
vector<string> removeDuplicates(vector<string> vector)
{
for (vector<string>:: const_iterator it = vector.begin(); it != vector.end(); ++it)
{
for (vector<string>:: const_iterator sit = vector.begin(); sit != vector.end(); ++sit)
{
if(it != sit)
{
if(*it == *sit)
{
vector.erase(it);
}
}
}
}
return vector;
}
I don't understand why it's giving me errors. I am kinda new to C++, so the question might sound kinda dumb. Is it because I am looping on the same vector?
You do not want to erase 1 item at a time. Use the remove-erase pattern which will erase all the elements that should be removed at 1 time, instead of several erase calls. In this case (as you appear to want to remove all duplicate elements), you can copy the vector to a std::set (which will not allow copies) and then assign it back.
The error message appears to be showing a bug in your implementation. std::vector::erase is supposed to be able to take a const_iterator (see quote from the standard below), but the error message you are seeing is indicating your implementation does not have it defined that way.
ยง 23.3.6.5
iterator erase(const_iterator position);
iterator erase(const_iterator first, const_iterator last);
You will also get undefined behavior with your current implementation as the iterator is invalidated once it is erased, so the it++ will not be valid in the loop condition.
If you switch to the more efficient removal methods, this would likely fix that problem as well.
Set Method
std::vector<std::string> vec;
// fill vector with duplicates
std::set<std::string> sVec(vec.begin(), vec.end()); // copies the vector into a set, duplicates removed
vec.assign(sVec.begin(), sVec.end()); // pushes the data in the set back to the vector
Remove/Unique-Erase Method (assuming order does not matter)
std::vector<std::string> vec;
// fill vector with duplicates
std::sort(vec.begin(), vec.end());
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
When an item in the vector is erased , use the valid iterator which the erase() returns.
like , iterator = vector.erase(iterator)
Also, when you happen to erase the last item in the vector, doing a ++iterator will not work.
This link may help vector erase iterator
I think, the conditions should be :
if(it != sit)
{
if(*it == *sit)
{
sit = vector.erase(sit)
}
else
{
++sit;
}
}
And don't increment sit in for loop
Remove the constness of the iterator which you are erasing. Make sit as vector::iterator.
Do this:
it = vector.erase(it);
I have a map which elements are vectors.I have to delete from these vectors all elements which are equal to special number num
std::map<size_t,std::vector<size_t> > myMap;
for (std::map<size_t,std::vector<size_t> >::iterator itMap = myMap.begin();itMap != myMap.end();++itMap )
{
for (std::vector<size_t>::iterator itVec = itMap->second.begin();itVec != itMap->second.end();)
{
auto itNextVec = itVec;
++itNextVec;
if (*itVec == num)
{
itMap->second.erase(itVec );
}
itVec = itNextVec;
}
}
The code causes run-time exepssion .In VS - vector iterators incompatible.
Can someone point what is the cause for that?
Thanks
std::vector::erase returns an iterator to the next position of the list, and so when you do an erase you should make your iterator equal to the returned value.
The only thing that you have to consider is that the returned iterator could be the end so you should check for that.
What I personally like to do is is after doing in an erase and I get the next iterator position, I go back to the previous position of the returned iterator and than call a continue on the for loop
Example:
#include <vector>
#include <iostream>
int main()
{
std::vector<int> myInt;
myInt.push_back(1);myInt.push_back(2);myInt.push_back(3);
for(auto iter = myInt.begin();
iter != myInt.end();
++iter)
{
if(*iter == 1)
{
iter = myInt.erase(iter);
if(iter != myInt.begin())
{
iter = std::prev(iter);
continue;
}
}
std::cout << *iter << std::endl;
}
}
But doing an erase inside of a iterator loop is frowned upon because it invalidates the old iterator and that could cause a lot of issues if you didn't plan for them.
erasing will invalidate the iterator
Iterator validity
Iterators, pointers and references pointing to position (or first) and beyond are
invalidated, with all iterators, pointers and references to elements before position (or
first) are guaranteed to keep referring to the same elements they were referring to
before the call.
You can't trivially erase an item from a collection while iterating over it. Think a little about it, your removing what itVec "points" to, after the removal itVec no longer "points" to an element, so it no longer have a "next" pointer.
If you check e.g. this reference, you will see that the erase function returns an iterator to the next element. Continue the loop with this one (without increasing it of course).
Consider either using a different collection class than vector or creating a new vector with the desired items removed rather than removing from existing vector.
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.
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.