What happens to an STL iterator after erasing it in VS, UNIX/Linux?

20,220

Solution 1

Yes, if you erase an iterator, that iterator gets a so-called singular value, which means it doesn't belong to any container anymore. You can't increment, decrement or read it out/write to it anymore. The correct way to do that loop is:

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

For containers that could invalidate other iterators when you erase one iterator, erase returns the next valid iterator. Then you do it with

it = T2pS.erase(it)

That's how it works for std::vector and std::deque, but not for std::map or std::set.

Solution 2

After you call erase on an iterator into a std::map, it is invalidated. This means that you cannot use it. Attempting to use it (e.g. by incrementing it) is invalid and can cause anything to happen (including a crash). For a std::map, calling erase on an iterator does not invalidate any other iterator so (for example) after this call, (so long as it was not T2pS.end()), it will be valid:

T2pS.erase( it++ );

Of course, if you use this approach, you won't want to unconditionally increment it in the for loop.

For this example, though, why bother to erase in the for loop? Why not just call T2pS.clear() at the end of the loop.

On the other hand, it looks like you have a raw pointer 'on the right' of the map, but the map appears to own the pointed to object. In this case, why not make the thing on the right of the map some sort of smart pointer, such as std::tr1::shared_ptr?

[Incidentally, I don't see any template parameters to map. Have you typedef'ed a specific instantiation of std::map as map in the local namespace?]

Share:
20,220
Gal Goldman
Author by

Gal Goldman

Education: M.Sc. in Mathematics B.Sc. in Mathematics &amp; Computer Science Occupation: Current: - CPU Product Development Team Leader | Program Manager at Intel Corporation Past: - Software Project leader at Intel Corporation - Software engineer at Elbit Systems LTD. Experience: Languages: C++/Qt/C#/.NET/C/ADA/Perl Platforms: UNIX/Linux/Windows

Updated on January 13, 2020

Comments

  • Gal Goldman
    Gal Goldman over 4 years

    Please consider the following scenario:

    
    map(T,S*) & GetMap(); //Forward decleration
    
    map(T, S*) T2pS = GetMap();
    
    for(map(T, S*)::iterator it = T2pS.begin(); it != T2pS.end(); ++it)
    {
        if(it->second != NULL)
        {
            delete it->second;
            it->second = NULL;
        }
        T2pS.erase(it);
        //In VS2005, after the erase, we will crash on the ++it of the for loop.
        //In UNIX, Linux, this doesn't crash.
    }//for
    

    It seems to me that in VS2005, after the "erase", the iterator will be equal to end(), hence the crash while trying to increment it. Are there really differences between compilers in the behavior presented here? If so, what will the iterator after the "erase" equal to in UNIX/Linux?

    Thanks...

  • A. Rex
    A. Rex over 15 years
    Isn't the type signature void erase(iterator pos) ?
  • A. Rex
    A. Rex over 15 years
    Aha! vector::erase(iterator) returns another iterator, while map::erase(iterator) does not. The OP discussed maps, but this is useful information also.
  • ChrisN
    ChrisN over 15 years
    In Visual C++, map::erase(iterator) returns an iterator, but this is nonstandard.
  • Ankit Roy
    Ankit Roy over 15 years
    Containers never invalidate iterators when an element's value is changed. When an element is inserted or deleted, some container types (e.g. vector) will invalidate iterators pointing at other elements, while most (e.g. set, map, list) will not.
  • Gal Goldman
    Gal Goldman over 15 years
    Correct me if I'm wrong, but there's no difference between: it = T2pS.erase(it++); to the way it's written in the for loop in the question, because in both cases "it" will be invalid by the time you ++.
  • Pieter
    Pieter over 15 years
    Ok, I'll correct you 'cause you're wrong ;-) in erase(it++) the argument is evaluated before the erase is called, it will be (correctly) incremented, it's previous (unincremented) value is returned to erase and that one becomes invalid, but it itself already points to the next element.
  • Dean Burge
    Dean Burge over 15 years
    Deleting a null pointer is a noop. There is no need to check for null.
  • Dean Burge
    Dean Burge over 15 years
    you might mention that std::list doesn't invalidate any iterator other than the iterator(s) pointing to the erased element(s), when calling the member erase() function.
  • Johannes Schaub - litb
    Johannes Schaub - litb over 15 years
    wilhelmtell, i thought it would put only more confusion in the game if i mention more containers. your point about deleting a nullpointer is good, imho. ill add it
  • vkaul11
    vkaul11 about 11 years
    In delete it->second; aren't you deleting the post incremented iterator pointing to second and I think you want to delete the 'it' before the post incremented value.
  • Wallace
    Wallace almost 10 years
    for deque, how about deque.erase(itor++)?