¿Qué sucede si llamas a erase () en un elemento del mapa mientras iteras de principio a fin?

133

En el siguiente código recorro un mapa y pruebo si un elemento necesita ser borrado. ¿Es seguro borrar el elemento y seguir iterando o necesito recolectar las claves en otro contenedor y hacer un segundo ciclo para llamar a erase ()?

map<string, SerialdMsg::SerialFunction_t>::iterator pm_it;
for (pm_it = port_map.begin(); pm_it != port_map.end(); pm_it++)
{
    if (pm_it->second == delete_this_id) {
        port_map.erase(pm_it->first);
    }
}

ACTUALIZACIÓN: Por supuesto, luego leí esta pregunta que no pensé que estaría relacionada, pero responde a mi pregunta.

Matthew Smith
fuente
Tenga en cuenta que la pregunta std::remove_ifno funciona constd:map
socketpair

Respuestas:

183

C ++ 11

Esto se ha solucionado en C ++ 11 (o borrar se ha mejorado / hecho coherente en todos los tipos de contenedores).
El método de borrado ahora devuelve el siguiente iterador.

auto pm_it = port_map.begin();
while(pm_it != port_map.end())
{
    if (pm_it->second == delete_this_id)
    {
        pm_it = port_map.erase(pm_it);
    }
    else
    {
        ++pm_it;
    }
}

C ++ 03

Borrar elementos en un mapa no invalida ningún iterador.
(aparte de los iteradores en el elemento que se eliminó)

En realidad, insertar o eliminar no invalida ninguno de los iteradores:

También vea esta respuesta:
Mark Ransom Technique

Pero debe actualizar su código:
en su código, incrementa pm_it después de llamar a borrar. En este punto es demasiado tarde y ya está invalidado.

map<string, SerialdMsg::SerialFunction_t>::iterator pm_it = port_map.begin();
while(pm_it != port_map.end())
{
    if (pm_it->second == delete_this_id)
    {
        port_map.erase(pm_it++);  // Use iterator.
                                  // Note the post increment.
                                  // Increments the iterator but returns the
                                  // original value for use by erase 
    }
    else
    {
        ++pm_it;           // Can use pre-increment in this case
                           // To make sure you have the efficient version
    }
}
Martin York
fuente
¿Se pm_it++garantiza que el orden de evaluación del incremento en la expresión postfix se ejecutará antes de ingresar la función?
David Rodríguez - dribeas
44
@David Rodríguez - dribeas: Sí. El estándar garantiza que todas las expresiones de argumentos se evaluarán completamente antes de que se llame a la función. Es el resultado del incremento posterior que se pasa a la función de borrado (). Entonces, sí, el incremento de publicación de pm_it se realizará antes de que se invoque erase ().
Martin York
NOTA: Casi línea por línea coincide con el ejemplo de contenedor asociativo en el artículo 9 "Efectivo STL" de Scott Meyer
Ogre Psalm33
for (auto pm_t = port_map.begin (); pm_it! = port_map.end ();) {...}
Andrey Syrokomskiy
44
@iboisver: en el vector. El uso de erase () invalida todos los iteradores de la matriz después del punto de borrado (no solo el final), esta es una propiedad de los Sequencecontenedores. La propiedad especial de los Associativecontenedores es que los iteradores no se invalidan al borrar o insertar (a menos que apunten al elemento que se borró). Vector y borrar iteradores de usign se cubre en detalle en la pregunta apropiada stackoverflow.com/a/3938847/14065
Martin York
12

Así es como hago eso ...

typedef map<string, string>   StringsMap;
typedef StringsMap::iterator  StrinsMapIterator;

StringsMap m_TheMap; // Your map, fill it up with data    

bool IsTheOneToDelete(string str)
{
     return true; // Add your deletion criteria logic here
}

void SelectiveDelete()
{
     StringsMapIter itBegin = m_TheMap.begin();
     StringsMapIter itEnd   = m_TheMap.end();
     StringsMapIter itTemp;

     while (itBegin != itEnd)
     {
          if (IsTheOneToDelete(itBegin->second)) // Criteria checking here
          {
               itTemp = itBegin;          // Keep a reference to the iter
               ++itBegin;                 // Advance in the map
               m_TheMap.erase(itTemp);    // Erase it !!!
          }
          else
               ++itBegin;                 // Just move on ...
     }
}
AlaaShaker
fuente
Si también elimina el final del vector (itEnd), la última comprobación (la condición while) se realizará contra un iterador invalidado (itEnd). No está bien.
Agostino
1

Así es como lo haría, aproximadamente:

bool is_remove( pair<string, SerialdMsg::SerialFunction_t> val )
{
    return val.second == delete_this_id;
}

map<string, SerialdMsg::SerialFunction_t>::iterator new_end = 
    remove_if (port_map.begin( ), port_map.end( ), is_remove );

port_map.erase (new_end, port_map.end( ) );

Hay algo extraño

val.second == delete_this_id

pero acabo de copiarlo de tu código de ejemplo.

Ravenspoint
fuente