Was passiert, wenn Sie erase () für ein Kartenelement aufrufen, während Sie von Anfang bis Ende iterieren?


133

Im folgenden Code durchlaufe ich eine Karte und teste, ob ein Element gelöscht werden muss. Ist es sicher, das Element zu löschen und weiter zu iterieren, oder muss ich die Schlüssel in einem anderen Container sammeln und eine zweite Schleife ausführen, um erase () aufzurufen?

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

UPDATE: Natürlich habe ich dann diese Frage gelesen, von der ich nicht dachte, dass sie verwandt wäre, aber ich beantworte meine Frage.


Bitte beachten Sie in Frage, dass std::remove_ifnicht funktioniertstd:map
Socketpair

Antworten:


183

C ++ 11

Dies wurde in C ++ 11 behoben (oder das Löschen wurde verbessert / für alle Containertypen konsistent gemacht).
Die Löschmethode gibt jetzt den nächsten Iterator zurück.

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

Durch das Löschen von Elementen in einer Karte werden keine Iteratoren ungültig.
(abgesehen von Iteratoren für das Element, das gelöscht wurde)

Das tatsächliche Einfügen oder Löschen macht keinen der Iteratoren ungültig:

Siehe auch diese Antwort:
Mark Ransom Technique

Sie müssen Ihren Code jedoch aktualisieren:
In Ihrem Code erhöhen Sie pm_it nach dem Aufruf von erase. Zu diesem Zeitpunkt ist es zu spät und ist bereits ungültig.

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

Wird die Reihenfolge der Auswertung des Inkrements im Postfix-Ausdruck pm_it++garantiert ausgeführt, bevor die Funktion eingegeben wird?
David Rodríguez - Dribeas

4
@ David Rodríguez - Dribeas: Ja. Der Standard garantiert, dass alle Argumentausdrücke vollständig ausgewertet werden, bevor die Funktion aufgerufen wird. Es ist das Ergebnis des Post-Inkrements, das an die Löschfunktion () übergeben wird. Ja, das Post-Inkrement von pm_it wird ausgeführt, bevor erase () aufgerufen wird.
Martin York

HINWEIS: Fast Zeile für Zeile entspricht dem Beispiel für einen assoziativen Container in Scott Meyers "Effective STL" Punkt 9.
Ogre Psalm33

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

4
@iboisver: Auf dem Vektor. Die Verwendung von erase () macht alle Iteratoren des Arrays nach dem Löschpunkt (nicht nur am Ende) ungültig. Dies ist eine Eigenschaft von SequenceContainern. Die besondere Eigenschaft von AssociativeContainern besteht darin, dass Iteratoren nicht durch Löschen oder Einfügen ungültig werden (es sei denn, sie zeigen auf ein Element, das gelöscht wurde). Vektor und Löschen usign Iteratoren wird ausführlich in der entsprechenden Frage stackoverflow.com/a/3938847/14065
Martin York

12

So mache ich das ...

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

Wenn Sie auch das Ende des Vektors löschen (itEnd), erfolgt die letzte Prüfung (die while-Bedingung) gegen einen ungültig gemachten Iterator (itEnd). Nicht gut.
Agostino

1

So würde ich es ungefähr machen:

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

Es ist etwas Seltsames

val.second == delete_this_id

aber ich habe es gerade aus Ihrem Beispielcode kopiert.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.