Problema con std :: map :: iterator después de llamar a erase ()

// erasing from map
#include <iostream>
#include <map>
using namespace std;

int main ()
{
  map<char,int> mymap;
  map<char,int>::iterator it(mymap.begin());

  // insert some values:
  mymap['a']=10;
  mymap['b']=20;
  mymap['c']=30;
  mymap['d']=40;
  mymap['e']=50;
  mymap['f']=60;

  it=mymap.find('a');
  mymap.erase (it);                   // erasing by iterator

  // show content:
  for (; it != mymap.end(); it++ )
    cout << (*it).first << " => " << (*it).second << endl;
  return 0;
}

Why does this give an output like

a => 10
b => 20
c => 30
d => 40
e => 50
f => 60

no debe "a => 10" be deleted anyways, but if I declare it = mymap.begin() in the for loop, everything is perfect. why?

program adapted from : http://www.cplusplus.com/reference/stl/map/erase/

preguntado el 08 de enero de 11 a las 21:01

5 Respuestas

Erasing an element of a map invalidates iterators pointing to that element (after all that element has been deleted). You shouldn't reuse that iterator.

Desde C ++ 11 erase() returns a new iterator pointing to the next element, which can be used to continue iterating:

it = mymap.begin();
while (it != mymap.end()) {
   if (something)
      it = mymap.erase(it);
   else
      it++;
}

Before C++11 you would have to manually advance the iterator to the next element before the deletion takes place, for example like this:

mymap.erase(it++);

This works because the post-increment side-effect of it++ pasa antes erase() deletes the element. Since this is maybe not immediately obvious, the C++11 variant above should be preferred.

Respondido el 11 de junio de 18 a las 20:06

Not working with G++: codepad.org/D2lApTLL . Problem is that the erase() method was originally in 1998 defined to return void. Afaik C++03 changed that, but still not supported by g++. - No en lista

Shouldn't your first code snippet read mymap.erase(++it) (pre-increment) instead as a no-no? - OlivierD

@OlivierD: That would delete not the current element but the following item. And then it would leave the iterator pointing at that deleted item, so it would have the same problem as the code in the question. - sth

I'm confused by the sentence. You say "You shouldn't reuse that iterator (OK), or advance the iterator to the next element before the deletion takes place (preincremento), for example like this" and then present an example with post-increment which is then repeated as an example of good code. I would expect an example of bad code following that sentence. - OlivierD

llamar erase() invalidates the iterator. In this case, what's happening is the iterator points to the residual value left behind in memory (but don't rely on this undefined behaviour!). Reset the iterator with it=mymap.begin() before the loop for the desired results.

http://codepad.org/zVFRtoV5

Esta respuesta shows how to erase elements while iterating over an std::map:

for(map<T, S*>::iterator it = T2pS.begin(); it != T2pS.end(); T2pS.erase(it++)) {
    // wilhelmtell in the comments is right: no need to check for NULL. 
    // delete of a NULL pointer is a no-op.
    if(it->second != NULL) {
        delete it->second;
            it->second = NULL;
    }
}

contestado el 23 de mayo de 17 a las 15:05

So is it not possible to use the iterator inside a for loop and delete elements based on some condition or in other words if I have 1000 elements in a map and I want to delete elements that satisfy a certain user defined condition, then each time I delete an element should I break the loop and start all over again ? - 0x0

@Sunil With std::map, the answer unfortunately is yes. Deleting an element restructures the tree to maintain its balance, so you can't even rely on everything to the right of the deleted element being in its original location as with some other std containers such as std::list. - Moinudin

@Sunil See sth's post; if you post-increment the iterator when you call erase, your iterator remains valid. Just make sure that in your for loop, you don't accidentally increment the iterator twice. @marcog That's not exactly correct. While the nodes may be re-balanced, their actual addresses in memory don't change; it's only their left/right/parent pointers (at least for red-black trees). sth's answer works. - Dawson

@toolbox: Incrementing it twice is the problem, don't you think. because when I do that, the next time in the for loop it checks for a condition and again increments the iterator. This means every time I delete an item I skip an item next to it. - 0x0

@Sunil See esta respuesta for the correct way to iterate through an std::map and erase elements at the same time. - Moinudin

Esto tiene que ver con la forma en que map is implemented. Let's say it's a tree of some sort, like:

class map_node {
    char key;
    int  value;
    map_node* next;
    ...
};

Cuando erase() the iterator, you remove the node from the tree and deallocate its space. But until that memory location is overwritten, the node's contents are still in memory. That's why you can get not only the value, but also the next element in the tree. Thus, your result is completely expected.

Respondido el 09 de enero de 11 a las 00:01

it is no longer valid after mymap.erase(it). This means, it can do whatever it wants.

Respondido el 09 de enero de 11 a las 00:01

"it" still points at the same location, erase does not update the iterator by itself, you have to do it, by resetting the iterator. In effect, "it" points to the old location which has been erased from the vector but still contains the old data.

Respondido el 09 de enero de 11 a las 00:01

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.