for(auto it = m.cbegin(); it != m.cend()/* not hoisted */;/* no increment */){if(must_delete){
m.erase(it++);// or "it = m.erase(it)" since C++11}else{++it;}}
Zwróć uwagę, że naprawdę chcemy fortutaj zwykłej pętli, ponieważ modyfikujemy sam kontener. Pętla zakresowa powinna być zarezerwowana dla sytuacji, w których zależy nam tylko na elementach. Składnia RBFL wyjaśnia to jasno, nawet nie ujawniając kontenera wewnątrz ciała pętli.
Edytować. Przed C ++ 11 nie można było usunąć stałych iteratorów. Tam musiałbyś powiedzieć:
for(std::map<K,V>::iterator it = m.begin(); it != m.end();){/* ... */}
Usunięcie elementu z kontenera nie jest sprzeczne ze stałością elementu. Analogicznie, zawsze było całkowicie uzasadnione, delete pgdzie pznajduje się wskaźnik do stałej. Stałość nie ogranicza życia; wartości const w C ++ mogą nadal przestać istnieć.
„nawet nie wystawiając pojemnika wewnątrz korpusu pętli” co masz na myśli?
Dani
2
@Dani: Cóż, porównajmy to z konstrukcją z XX wieku for (int i = 0; i < v.size(); i++). Tutaj musimy powiedzieć v[i]wewnątrz pętli, czyli musimy wyraźnie wspomnieć o kontenerze. Z drugiej strony RBFL wprowadza zmienną pętli, która jest bezpośrednio używana jako wartość, więc nie jest wymagana znajomość kontenera wewnątrz pętli. Jest to wskazówka co do zamierzonego użycia RBFL dla pętli, które nie muszą wiedzieć o kontenerze. Kasowanie to zupełnie odwrotna sytuacja, w której chodzi o pojemnik.
Kerrek SB,
3
@skyhisi: Rzeczywiście. Jest to jedno z legalnych zastosowań post-inkrementacji: najpierw inkrementacja w itcelu uzyskania następnego, prawidłowego iteratora, a następnie usunięcie starego. To nie działa w drugą stronę!
Kerrek SB,
5
Czytałem gdzieś, że w C ++ 11, it = v.erase(it);teraz działa też dla map, czyli erase () na wszystkich elementach asocjacyjnych zwraca teraz następny iterator. Więc stary kludge, który wymagał post-inkrementacji ++ w ramach delete (), nie jest już potrzebny. To (jeśli prawda) jest Dobra Rzecz, ponieważ kludge polegał na zastępowanej magii wywołania funkcji po inkrementacji, „naprawionej” przez początkujących opiekunów, aby usunąć przyrost z wywołania funkcji lub zamienić go do preinkrement „bo to tylko kwestia stylu” itp.
Dewi Morgan,
3
dlaczego miałbyś dzwonić it++do bloków ifielse ? czy nie wystarczy nazwać to raz po tych?
nburk
25
Osobiście wolę ten wzór, który jest nieco jaśniejszy i prostszy kosztem dodatkowej zmiennej:
for(auto it = m.cbegin(), next_it = it; it != m.cend(); it = next_it){++next_it;if(must_delete){
m.erase(it);}}
Zalety tego podejścia:
element zwiększający pętlę for ma sens jako element zwiększający;
operacja kasowania jest prostym kasowaniem, a nie jest mieszana z logiką inkrementacji;
po pierwszym wierszu ciała pętli, znaczenie iti next_itpozostaje niezmienne przez całą iterację, co pozwala na łatwe dodawanie dodatkowych instrukcji odnoszących się do nich bez zastanawiania się, czy będą działać zgodnie z przeznaczeniem (z wyjątkiem oczywiście, że nie można ich użyć itpo skasowaniu) .
Mogę wymyślić inną zaletę, jeśli pętla wywołuje kod, który usuwa iterację tego wpisu lub poprzednie (a pętla nie jest tego świadoma), będzie działać bez żadnych szkód. Jedynym ograniczeniem jest to, czy coś wymazuje to, na co wskazuje next_it lub następcy. Całkowicie wyczyszczoną listę / mapę można również przetestować.
Larswad
Ta odpowiedź jest prosta i jasna, nawet jeśli pętla jest bardziej złożona i ma wiele poziomów logiki, aby zdecydować, czy usunąć, czy wykonać inne różne zadania. Zaproponowałem jednak zmianę, aby było to trochę prostsze. "next_it" można ustawić na "it" w init for, aby uniknąć literówek, a ponieważ zarówno instrukcje init, jak i iteracji ustawiają go i next_it na te same wartości, nie trzeba mówić "next_it = it;" na początku pętli.
cdgraham
1
Pamiętaj o każdym, kto użyje tej odpowiedzi: Musisz mieć „++ next_it” wewnątrz pętli for, a nie w wyrażeniu iteracji. Jeśli spróbujesz przenieść to do wyrażenia iteracji jako „it = next_it ++”, to w ostatniej iteracji, gdy „it” zostanie ustawione na „m.cend ()”, spróbujesz wykonać iterację „next_it” przeszłość „m.cend ()”, która jest błędna.
cdgraham
6
W skrócie „Jak usunąć z mapy podczas iteracji?”
Ze starą mapą impl: Nie możesz
Z nową mapą impl: prawie tak, jak sugerował @KerrekSB. Ale są pewne problemy ze składnią w tym, co opublikował.
Z impl mapy GCC (uwaga GXX_EXPERIMENTAL_CXX0X ):
#ifdef __GXX_EXPERIMENTAL_CXX0X__
// _GLIBCXX_RESOLVE_LIB_DEFECTS// DR 130. Associative erase should return an iterator./**
* @brief Erases an element from a %map.
* @param position An iterator pointing to the element to be erased.
* @return An iterator pointing to the element immediately following
* @a position prior to the element being erased. If no such
* element exists, end() is returned.
*
* This function erases an element, pointed to by the given
* iterator, from a %map. Note that this function only erases
* the element, and that if the element is itself a pointer,
* the pointed-to memory is not touched in any way. Managing
* the pointer is the user's responsibility.
*/iterator
erase(iterator __position){return_M_t.erase(__position);}#else/**
* @brief Erases an element from a %map.
* @param position An iterator pointing to the element to be erased.
*
* This function erases an element, pointed to by the given
* iterator, from a %map. Note that this function only erases
* the element, and that if the element is itself a pointer,
* the pointed-to memory is not touched in any way. Managing
* the pointer is the user's responsibility.
*/void
erase(iterator __position){_M_t.erase(__position);}#endif
Przykład ze starym i nowym stylem:
#include<iostream>#include<map>#include<vector>#include<algorithm>usingnamespace std;typedefmap<int,int> t_myMap;typedefvector<t_myMap::key_type> t_myVec;int main(){
cout <<"main() ENTRY"<< endl;
t_myMap mi;
mi.insert(t_myMap::value_type(1,1));
mi.insert(t_myMap::value_type(2,1));
mi.insert(t_myMap::value_type(3,1));
mi.insert(t_myMap::value_type(4,1));
mi.insert(t_myMap::value_type(5,1));
mi.insert(t_myMap::value_type(6,1));
cout <<"Init"<< endl;for(t_myMap::const_iterator i = mi.begin(); i != mi.end(); i++)
cout <<'\t'<< i->first <<'-'<< i->second << endl;
t_myVec markedForDeath;for(t_myMap::const_iterator it = mi.begin(); it != mi.end(); it++)if(it->first >2&& it->first <5)
markedForDeath.push_back(it->first);for(size_t i =0; i < markedForDeath.size(); i++)// old erase, returns void...
mi.erase(markedForDeath[i]);
cout <<"after old style erase of 3 & 4.."<< endl;for(t_myMap::const_iterator i = mi.begin(); i != mi.end(); i++)
cout <<'\t'<< i->first <<'-'<< i->second << endl;for(auto it = mi.begin(); it != mi.end();){if(it->first ==5)// new erase() that returns iter..
it = mi.erase(it);else++it;}
cout <<"after new style erase of 5"<< endl;// new cend/cbegin and lambda..
for_each(mi.cbegin(), mi.cend(),[](t_myMap::const_reference it){cout <<'\t'<< it.first <<'-'<< it.second << endl;});return0;}
wydruki:
main() ENTRY
Init1-12-13-14-15-16-1
after old style erase of 3&4..1-12-15-16-1
after new style erase of 51-12-16-1Process returned 0(0x0) execution time :0.021 s
Press any key to continue.
Nie rozumiem. Z czym jest problem mi.erase(it++);?
lvella
1
@lvella patrz op. „Jeśli użyję map.erase, spowoduje to unieważnienie iteratorów”.
Kashyap
Twoja nowa metoda nie zadziała, jeśli po wymazaniu mapa stanie się pusta. W takim przypadku iterator zostanie unieważniony. Więc wkrótce po skasowaniu lepiej jest wstawić if(mi.empty()) break;.
Rahat Zaman
4
Wersja robocza C ++ 20 zawiera wygodną funkcję std::erase_if.
Możesz więc użyć tej funkcji, aby zrobić to jako jedną linijkę.
std::map<K, V> map_obj;//calls needs_removing for each element and erases it, if true was reuturned
std::erase_if(map_obj,needs_removing);//if you need to pass only part of the key/value pair
std::erase_if(map_obj,[](auto& kv){return needs_removing(kv.first);});
Całkiem smutne, co? Sposób, w jaki zwykle to robię, to budowanie kontenera iteratorów zamiast usuwania podczas przechodzenia. Następnie przejdź przez kontener i użyj map.erase ()
std::map<K,V>map;
std::list< std::map<K,V>::iterator> iteratorList;for(auto i :map){if( needs_removing(i)){
iteratorList.push_back(i);}}for(auto i : iteratorList){map.erase(*i)}
Odpowiedzi:
Standardowy idiom usuwania kontenerów asocjacyjnych:
Zwróć uwagę, że naprawdę chcemy
for
tutaj zwykłej pętli, ponieważ modyfikujemy sam kontener. Pętla zakresowa powinna być zarezerwowana dla sytuacji, w których zależy nam tylko na elementach. Składnia RBFL wyjaśnia to jasno, nawet nie ujawniając kontenera wewnątrz ciała pętli.Edytować. Przed C ++ 11 nie można było usunąć stałych iteratorów. Tam musiałbyś powiedzieć:
Usunięcie elementu z kontenera nie jest sprzeczne ze stałością elementu. Analogicznie, zawsze było całkowicie uzasadnione,
delete p
gdziep
znajduje się wskaźnik do stałej. Stałość nie ogranicza życia; wartości const w C ++ mogą nadal przestać istnieć.źródło
for (int i = 0; i < v.size(); i++)
. Tutaj musimy powiedziećv[i]
wewnątrz pętli, czyli musimy wyraźnie wspomnieć o kontenerze. Z drugiej strony RBFL wprowadza zmienną pętli, która jest bezpośrednio używana jako wartość, więc nie jest wymagana znajomość kontenera wewnątrz pętli. Jest to wskazówka co do zamierzonego użycia RBFL dla pętli, które nie muszą wiedzieć o kontenerze. Kasowanie to zupełnie odwrotna sytuacja, w której chodzi o pojemnik.it
celu uzyskania następnego, prawidłowego iteratora, a następnie usunięcie starego. To nie działa w drugą stronę!it = v.erase(it);
teraz działa też dla map, czyli erase () na wszystkich elementach asocjacyjnych zwraca teraz następny iterator. Więc stary kludge, który wymagał post-inkrementacji ++ w ramach delete (), nie jest już potrzebny. To (jeśli prawda) jest Dobra Rzecz, ponieważ kludge polegał na zastępowanej magii wywołania funkcji po inkrementacji, „naprawionej” przez początkujących opiekunów, aby usunąć przyrost z wywołania funkcji lub zamienić go do preinkrement „bo to tylko kwestia stylu” itp.it++
do blokówif
ielse
? czy nie wystarczy nazwać to raz po tych?Osobiście wolę ten wzór, który jest nieco jaśniejszy i prostszy kosztem dodatkowej zmiennej:
Zalety tego podejścia:
it
inext_it
pozostaje niezmienne przez całą iterację, co pozwala na łatwe dodawanie dodatkowych instrukcji odnoszących się do nich bez zastanawiania się, czy będą działać zgodnie z przeznaczeniem (z wyjątkiem oczywiście, że nie można ich użyćit
po skasowaniu) .źródło
W skrócie „Jak usunąć z mapy podczas iteracji?”
Z impl mapy GCC (uwaga GXX_EXPERIMENTAL_CXX0X ):
Przykład ze starym i nowym stylem:
wydruki:
źródło
mi.erase(it++);
?if(mi.empty()) break;
.Wersja robocza C ++ 20 zawiera wygodną funkcję
std::erase_if
.Możesz więc użyć tej funkcji, aby zrobić to jako jedną linijkę.
źródło
Całkiem smutne, co? Sposób, w jaki zwykle to robię, to budowanie kontenera iteratorów zamiast usuwania podczas przechodzenia. Następnie przejdź przez kontener i użyj map.erase ()
źródło
Zakładając C ++ 11, oto treść pętli jednowierszowej, jeśli jest to zgodne z Twoim stylem programowania:
Kilka innych drobnych zmian w stylu:
Map::const_iterator
), jeśli to możliwe / wygodne, przed użyciemauto
.using
dla typów szablonów, abyMap::const_iterator
ułatwić odczytywanie / utrzymywanie typów pomocniczych ( ).źródło