Cosa succede se si chiama erase () su un elemento della mappa mentre si scorre dall'inizio alla fine?


133

Nel codice seguente cerco una mappa e controllo se un elemento deve essere cancellato. È sicuro cancellare l'elemento e continuare a scorrere o devo raccogliere le chiavi in ​​un altro contenitore ed eseguire un secondo ciclo per chiamare il cancella ()?

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

AGGIORNAMENTO: Naturalmente, ho letto questa domanda che non pensavo fosse correlata, ma risponde alla mia domanda.


Si prega di notare in questione che std::remove_ifnon funziona constd:map
socketpair

Risposte:


183

C ++ 11

Questo problema è stato risolto in C ++ 11 (o la cancellazione è stata migliorata / resa coerente in tutti i tipi di contenitore).
Il metodo di cancellazione ora restituisce il successivo iteratore.

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

La cancellazione di elementi in una mappa non invalida alcun iteratore.
(a parte gli iteratori sull'elemento che è stato eliminato)

L'inserimento o l'eliminazione effettiva non invalida nessuno degli iteratori:

Vedi anche questa risposta:
Mark Ransom Technique

Ma devi aggiornare il tuo codice:
nel tuo codice aumenti pm_it dopo aver chiamato cancella. A questo punto è troppo tardi ed è già invalidato.

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

L'ordine di valutazione dell'incremento nell'espressione postfix è pm_it++garantito per essere eseguito prima di inserire la funzione?
David Rodríguez - dribeas,

4
@David Rodríguez - dribeas: Sì. Lo standard garantisce che tutte le espressioni degli argomenti saranno valutate completamente prima che venga chiamata la funzione. È il risultato dell'incremento post che viene passato alla funzione di cancellazione (). Quindi sì, l'incremento post di pm_it verrà eseguito prima che venga chiamato erase ().
Martin York,

NOTA: Quasi riga per riga corrisponde all'esempio del contenitore associativo
nell'articolo

per (auto pm_t = port_map.begin (); pm_it! = port_map.end ();) {...}
Andrey Syrokomskiy

4
@iboisver: sul vettore. L'uso di erase () invalida tutti gli iteratori dell'array dopo il punto di cancellazione (non solo la fine), questa è una proprietà dei Sequencecontenitori. La proprietà speciale dei Associativecontenitori è che gli iteratori non sono invalidati dalla cancellazione o dall'inserimento (a meno che non puntino sull'elemento che è stato cancellato). Vector e cancella gli iteratori usign sono trattati in dettaglio nella domanda appropriata stackoverflow.com/a/3938847/14065
Martin York

12

Ecco come lo faccio ...

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

Se si elimina anche la fine del vettore (itEnd), l'ultimo controllo (la condizione while) sarà confrontato con un iteratore non valido (itEnd). Non bene.
Agostino,

1

Ecco come lo farei, approssimativamente:

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( ) );

C'è qualcosa di strano

val.second == delete_this_id

ma l'ho appena copiato dal tuo codice di esempio.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.