Can I reuse an invalidated iterator? - c++

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;
}
}

Related

Iterating through a vector for comparison in C++

I'm trying to write a program where given a vector, you use iterators to compare the first and last number of the vector, then moves in and compares the next ones. I wrote the for loop to do that, but am unsure how to make it stop once they reach the center of the vector.
For the for loop I have:
for (a = v.begin(), b = v.rbegin(); a != v.end(), b != v.rend(); a++, b++)
where a is the forward iterator and b is a backwards iterator.
My assumption is that I need to change the condition of the for loop, but I'm unsure to what.
So bear in mind that std::vector<T>::iterator is a random-access iterator, which means that it has operator< defined.
Using this, and using the std::reverse_iterator<Iterator>::base() member function, we can rewrite your for-loop to the following:
auto a = v.begin();
auto b = v.rbegin();
for (; a < b.base(); ++a, ++b)
{
// Do stuff...
}
First of all you need to use && and not the , operator in the comparison, which doesn't do what you think it does.
For your specific question you just keep going until both iterators reach each other, you can obtain the underlying std::iterator of a std::reverse_iterator through base(), eg:
template<typename T> bool isPalindrome(const std::vector<T>& data)
{
for (auto it = data.begin(), it2 = data.rbegin(); it != data.end() && it2 != data.rend() &&
it != it2.base(); ++it, ++it2)
if (*it != *it2)
return false;
return true;
}

Delete Elements from Vector Inside Loop

I have a vector, words_in_family, of type: vector<vector<string>>. I am trying to delete every element of words_in_family that is not equal to the string vector largest_family, but am having issues and am unsure of why. Any help is appreciated.
for (int i = words_in_family.size() - 1; i >= 0; i--)
{
if (words_in_family[i] != largest_family)
{
words_in_family.erase(words_in_family[i]);
}
}
erase method doesn't take value. Instead use iterator as parameter for erase method:
vector<vector<string>>::iterator it = words_in_family.begin();
for (int i = words_in_family.size() - 1; i >= 0; i--)
{
if (words_in_family[i] != largest_family)
{
words_in_family.erase(it+i);
}
}
iterator erase (iterator position);
iterator erase (iterator first, iterator last);
Note that the iterators for std::vector are random access iterators so you can add/subtract integral values to get other valid iterators.
C++ std::vector erase an element, in your case:
Erase an element from a vector(words_in_family) whose value is equal to largest_family:
std::vector<std::vector<std::string>>::iterator Itr;
for(Itr = Words_in_family.begin(); Itr != Words_in_family.end();)
{
if(*Itr == largest_family)
{
Itr = Words_in_family.erase(Itr);
}
else
{
Itr++;
}
}
words_in_family.erase(words_in_family.begin()+i, words_in_family.begin()+i+1);
erase takes iterator as an argument

Getting run time error between cross- plateform with same C++ code

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++;
}

Weird seg fault when erasing from a map

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

Removing elements from a C++ map through a for-loop

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