I have the following code:
//update it in the map
std::map<std::string, std::string>::iterator it;
for(it = spreadsheets.at(i).cells.begin(); it != spreadsheets.at(i).cells.end(); ++it)
{
if(it->first == change.first)
{
if(change.second == "")
{
spreadsheets.at(i).cells.erase(change.first);
}
else
{
it->second = change.second;
}
}
}
The code above runs perfectly on my mac however when I run in on a linux computer it throws a seg fault on spreadsheets.at(i).cells.erase(change.first);
Any idea whats causing this error? Ive tried changing erase(change.first) to erase(it) and I am still getting the seg fault.
Because when you erase from the container, your iterator is no longer valid, yet your loop continues.
You could change your loop to:
std::map<std::string, std::string>::iterator it = spreadsheets.at(i).cells.begin();
while (it != spreadsheets.at(i).cells.end())
{
if(it->first == change.first)
{
if(change.second == "")
{
spreadsheets.at(i).cells.erase(it++); //Post increment returns a copy pointing at the current element, while it already points to the next element and thus stays valid after erase
}
else
{
it->second = change.second;
++it;
}
}
else
++it;
}
Now that I think of it, why do you erase with first element of the pair the iterator is pointing to, ie:
spreadsheets.at(i).cells.erase(change.first);
instead of
spreadsheets.at(i).cells.erase(it);
It's less efficient, as another lookup has to be made.
From the documentation of std::map::erase:
References and iterators to the erased elements are invalidated. Other references and iterators are not affected.
Still your loop goes on and you increment your (now invalid) iterator.
Fix: increment your iterator another way, eg.:
std::map<std::string, std::string>::iterator it;
for (it = spreadsheets.at(i).cells.begin(); it != spreadsheets.at(i).cells.end();/*NOTE: no increment here*/)
{
if (it->first == change.first)
{
if (change.second == "")
{
it = spreadsheets.at(i).cells.erase(it); // C++11 only
// in both C++03 and C++11: spreadsheets.at(i).cells.erase(it++);
}
else
{
it->second = change.second;
++it;
}
}
else
++it;
}
Or to avoid confusion because of the numerous execution paths (the very same confusion that made me forget the last else on my first try): just copy the iterator, increment the original one, and then use the copy. This may look overkill in your case but for more complex loops this is sometimes the only way to stay sane. ;)
std::map<std::string, std::string>::iterator it;
for (it = spreadsheets.at(i).cells.begin(); it != spreadsheets.at(i).cells.end();/*NOTE: no increment here*/)
{
std::map<std::string, std::string>::iterator this_it = it++;
if (this_it->first == change.first)
{
if (change.second == "")
{
spreadsheets.at(i).cells.erase(this_it);
}
else
{
this_it->second = change.second;
}
}
}
After element being erased from map pointing to this element will become invalidated. Thus spreadsheets.at(i).cells.erase(change.first); renders it invalid. See Iterator Invalidation Rules
References and iterators to the erased elements are invalidated.
//update it in the map
std::map<std::string, std::string>::iterator it;
for(it = spreadsheets.at(i).cells.begin(); it != spreadsheets.at(i).cells.end(); ++it)
{
if(it->first == change.first)
{
if(change.second == "")
{
spreadsheets.at(i).cells.erase(it--);
}
else
{
it->second = change.second;
}
}
}
At the moment you do spreadsheets.at(i).cells.erase(change.first); the iterator in the std::map (at the current change.first key) is invalidated. So, when you do it++, it is undefined behaviour.
cf Rules for Iterator Invalidation for rules about invalidation of iterators in standard containers
Increment the iterator before you erase it. Why didn't it happen on the Mac? Who knows.. different OS, different behaviour.
SO
Related
I am erasing element from the C++ multimap using following code which is running perfectly under Linux, but throwing expression set/map is not incrementable on Windows.
void delete_entries(multimap<int, pair<int, int> > &m, int i, int j) {
// This function deletes entries from multimap where box number == i and Ball index ==j
multimap<int, pair<int, int> > ::iterator it = m.begin();
while (it != m.end()) {
if ((*it).second.first == i || (*it).second.second == j)
m.erase(it);
it++;
}
}
while (it != m.end()) {
if ((*it).second.first == i || (*it).second.second == j)
m.erase(it);
it++; //if the previous condition holds true, 'it' is invalidated
}
An erased iterator becomes invalidated. And incrementing an invalidated iterator is Undefined behavior.
Undefined behavior means that even if it seemingly works, there are no more guarantees to the behavior of your entire program. (this applies to the seemingly working one in linux too; you should be really happy that it crashed on MSVC)
For the correct way to erase, see the other answers here and this question: How to remove from a map while iterating it?
Since C++11, erase returns the iterator following the last removed element.
void delete_entries(multimap<int, pair<int, int> > &m, int i, int j) {
// this function deletes entries from multimap where box number == i and Ball index ==j
multimap<int, pair<int, int> > ::iterator it = m.begin();
while (it != m.end()) {
if ((*it).second.first == i || (*it).second.second == j)
it = m.erase(it);
else
it++;
}
}
The iterator becomes invalid after erase. Since C++11, you can do:
while (it != m.end()) {
if ((*it).second.first == i || (*it).second.second == j)
it = m.erase(it);
else
it++;
}
I am having an issue and I think it is because of the iterators being invalidated. However I use the iterator from erase() to resume iterating other the structure. When erase() when I try to increment after erase() is called the first time I get the following error
'vector iterator not incrementable '
std::map<uint32_t, std::vector<std::pair<boost::uuids::uuid, tvshared::SecureIPCCallbackHandlePtr>>>::iterator itMap;
std::vector<std::pair<boost::uuids::uuid, tvshared::SecureIPCCallbackHandlePtr>>::iterator itVector;
{
tvstd::lock_guard_mutex l(m_ConnectionsMutex);
itMap = m_Connections.find(static_cast<uint32_t>(pcp->ProcessID()));
if (itMap != m_Connections.end())
{
for (itVector = itMap->second.begin(); itVector != itMap->second.end(); ++itVector)
{
if (commadUUID == itVector->first)
{
itVector->second.reset();
itVector = m_Connections[static_cast<uint32_t>(pcp->ProcessID())].erase(itVector);
}
}
}
}
Can anyone see where I am going wrong?
erase returns an iterator pointing to the new location of the element that followed the last element erased by the function call. This is the container end if the operation erased the last element in the sequence.
so if you erase you do not need to increment your iterator
for (itVector = itMap->second.begin(); itVector != itMap->second.end(); )
{
if (commadUUID == itVector->first)
{
itVector->second.reset();
itVector = m_Connections[static_cast<uint32_t>(pcp->ProcessID())].erase(itVector);
}
else
{
++itVector
}
}
This solved my issue, I just have to call break after i erase but once i erase i do not need to loop to the end of the list. (#Aleexander solution also works)
std::map<uint32_t, std::vector<std::pair<boost::uuids::uuid, tvshared::SecureIPCCallbackHandlePtr>>>::iterator itMap;
std::vector<std::pair<boost::uuids::uuid, tvshared::SecureIPCCallbackHandlePtr>>::iterator itVector;
{
tvstd::lock_guard_mutex l(m_ConnectionsMutex);
itMap = m_Connections.find(static_cast<uint32_t>(pcp->ProcessID()));
if (itMap != m_Connections.end())
{
for (itVector = itMap->second.begin(); itVector != itMap->second.end(); ++itVector)
{
if (commadUUID == itVector->first)
{
itVector->second.reset();
itVector = m_Connections[static_cast<uint32_t>(pcp->ProcessID())].erase(itVector);
break;
}
}
}
}
The following code works (and admittedly is not the most efficient way to go about this routine). My question is this, is it discouraged to reuse the iterator as I have done here? Might it produce strange behavior? If so, why?
std::map<char, int> map;
map['a'] = 10;
map['b'] = 30;
map['c'] = 50;
map['d'] = 70;
std::map<char, int>::iterator iterator = map.begin();
for (; iterator != map.end(); iterator++) {
if (iterator->second == 30 || iterator->second == 50) {
map.erase(iterator);
iterator = map.begin();
}
}
No, there is nothing wrong with re-assigning to the iterator and reusing it, because after the assignment operator is run, the old value is completely overwritten.
iterator = map.begin();
You're not using an invalidated iterator, but your logic is flawed. To fix it, make a small change to your code; only increment the iterator if you haven't erased an element during the current iteration. With your current code, assume that the first two elements in the map meet the erasure criterion. Then the second one will be left unerased because you increment past it on the second iteration through the loop.
for (; iterator != map.end();) {
if (iterator->second == 30 || iterator->second == 50) {
map.erase(iterator);
iterator = map.begin();
} else {
++iterator;
}
}
If your compiler supports C++11 you can do this instead to erase elements from the map
for (; iterator != map.end(); ) {
if (iterator->second == 30 || iterator->second == 50) {
iterator = map.erase(iterator);
} else {
++iterator;
}
}
I have a class Circle whose instances I keep track of with these:
Circle *f1;
vector<Circle> list;
vector<Circle>::iterator it;
I have managed to create multiple Circles and got them to move around. How can I erase a specific instance of Circle? For example, if a certain circle hits a wall, then it should be erased. I've looked around at other questions and I even tried the code they gave out and no luck. Here's what I've got at the moment:
for (it = list.begin(); it != list.end(); ++it) {
it->x += 1;
if (it->x == ofGetWindowWidth()) {
list.erase(it);
}
}
I have gotten other statements to work with the if statement such as reversing the direction of their movement. list.erase(it); was a line of code I got from here and I don't understand why it crashes my program.
for (it = list.begin(); it != list.end(); /* nothing here */) {
it->x += 1;
if (it->x == ofGetWindowWidth()) {
it = list.erase(it);
} else {
++it;
}
}
The problem with your original code is that erasing an element invalidates the iterator to that element - the very same iterator you are trying to increment next. This exhibits undefined behavior.
list.erase invalidates iterators to the erased element. Therefore, after you erase the element pointed to by "it", "it" is invalidated and the ++it, which follows after the for loops body, can crash your program.
Rewriting your code to something similiar to the following should prevent your crash:
for(it=list.begin();it!=list.end(); ) {
//your code
if(it->x==ofGetWindowWidth())
it=list.erase(it);
else
++it;
}
The problem with the above code using erase() is that it invalidates the content of it when the element is being erase. You can use, e.g., this instead:
for (it = list.begin(); it != list.end(); ) {
it->x += 1;
if (it->x == ofGetWindowWidth()) {
list.erase(it++);
}
else {
++it;
}
}
The branch using erase() moves the kept iterator it off its current location before erase()ing the element. Only the temporary object return from it++ gets invalidated. Of course, for this loop to work, you can't unconditionally increment it, i.e., the non-erase()ing branch needs its own increment.
You could use erase with remove_if. This also works for removal of multiple elements. In your case it's
list.erase(std::remove_if(list.begin(), list.end(),
[](const Circle& c){return c.x == ofGetWindowWidth();},list.end()),
Example with integers:
#include <algorithm>
#include <vector>
#include <iostream>
int main()
{
std::vector<int> str1 = {1,3,5,7};
str1.erase(std::remove_if(str1.begin(), str1.end(),
[](int x){return x<4 && x>2;}), str1.end());
for(auto i : str1) std::cout << i ;
}
prints 157
My STL is a bit rusty, so forgive me for asking a possibly trivial question. Consider the following piece of code:
map<int,int> m;
...
for (auto itr = m.begin(); itr != m.end(); ++itr) {
if (itr->second == 0) {
m.erase(itr);
}
}
The question is: Is it safe to erase elements while looping over the map?
Yes, but not the way you do it. You're invalidating itr when you erase, then incrementing the invalid iterator.
auto itr = m.begin();
while (itr != m.end()) {
if (itr->first == 0) {
m.erase(itr++);
} else {
++itr;
}
}
I think that you shouldn't use removed iterator at all - in case of lists this causes serious problems, shouldn't be different for maps.
EDIT by Matthieu M: this code is well-formed in C++0x and allowed as an extension by MSVC.
map<int,int> m;
...
auto itr = m.begin();
while (itr != m.end())
{
if (itr->second == 0) {
itr = m.erase(itr);
}
else
{
itr++;
}
}
For the example given, It would actually be easier to use the erase overload that takes a key as an argument. This function erases all elements in the map with the given key (for a map, this is always either zero or one element)
map<int,int> m;
// ...
m.erase(0); // erase all elements with key equivalent to 0