Co się stanie, jeśli wywołasz funkcję erase () na elemencie mapy podczas iteracji od początku do końca?


133

W poniższym kodzie wykonuję pętlę przez mapę i sprawdzam, czy element musi zostać usunięty. Czy można bezpiecznie wymazać element i kontynuować iterację, czy też muszę zebrać klucze w innym kontenerze i wykonać drugą pętlę, aby wywołać funkcję 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);
    }
}

AKTUALIZACJA: Oczywiście przeczytałem wtedy to pytanie, które nie wydawało mi się powiązane, ale odpowiada na moje pytanie.


Zwróć uwagę na pytanie, które std::remove_ifnie działa zstd:map
socketpair

Odpowiedzi:


183

C ++ 11

Zostało to naprawione w C ++ 11 (lub wymazywanie zostało ulepszone / ujednolicone we wszystkich typach kontenerów).
Metoda erase zwraca teraz następny iterator.

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

Wymazywanie elementów na mapie nie unieważnia żadnych iteratorów.
(oprócz iteratorów na usuniętym elemencie)

W rzeczywistości wstawianie lub usuwanie nie unieważnia żadnego z iteratorów:

Zobacz także odpowiedź:
Technika oznaczania okupu

Ale musisz zaktualizować swój kod: w
swoim kodzie zwiększasz wartość pm_it po wywołaniu funkcji erase. W tym momencie jest już za późno i jest już unieważniony.

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

Czy kolejność obliczania przyrostu w wyrażeniu przyrostkowym jest pm_it++gwarantowana przed wprowadzeniem funkcji?
David Rodríguez - dribeas

4
@David Rodríguez - dribeas: Tak. Standard gwarantuje, że wszystkie wyrażenia argumentów zostaną w pełni ocenione przed wywołaniem funkcji. Jest to wynik inkrementacji postu, który jest przekazywany do funkcji erase (). Więc tak, przyrost postu pm_it zostanie wykonany przed wywołaniem funkcji erase ().
Martin York,

UWAGA: Prawie wiersz po wierszu pasuje do przykładu kontenera asocjacyjnego w „Efektywnym STL” Scotta Meyera w punkcie 9.
Ogre Psalm33

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

4
@iboisver: w wektorze. Użycie erase () unieważnia wszystkie iteratory tablicy po punkcie kasowania (nie tylko koniec), jest to właściwość Sequencekontenerów. Specjalną właściwością Associativekontenerów jest to, że iteratory nie są unieważniane przez wymazywanie lub wstawianie (chyba że wskazują na element, który został usunięty). Iteratory wektorów i wymazywania są szczegółowo omówione w odpowiednim pytaniu stackoverflow.com/a/3938847/14065
Martin York,

12

Oto jak to robię ...

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

Jeśli usuniesz również koniec wektora (itEnd), to ostatnia kontrola (warunek while) będzie dotyczyła unieważnionego iteratora (itEnd). Niedobrze.
Agostino

1

Tak bym to zrobił, w przybliżeniu:

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

Jest w tym coś dziwnego

val.second == delete_this_id

ale właśnie skopiowałem to z twojego przykładowego kodu.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.