Die Standardsprache zum Löschen von assoziativen Containern:
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;}}
Beachten Sie, dass wir hier wirklich eine gewöhnliche forSchleife wollen , da wir den Container selbst modifizieren. Die bereichsbasierte Schleife sollte strikt für Situationen reserviert werden, in denen wir uns nur um die Elemente kümmern. Die Syntax für die RBFL macht dies deutlich, indem der Container nicht einmal innerhalb des Schleifenkörpers verfügbar gemacht wird.
Bearbeiten. Vor C ++ 11 konnten Sie Konstanteniteratoren nicht löschen. Dort müsste man sagen:
for(std::map<K,V>::iterator it = m.begin(); it != m.end();){/* ... */}
Das Löschen eines Elements aus einem Container steht nicht im Widerspruch zur Konstanz des Elements. In Analogie dazu war es immer absolut legitim, delete pwo psich ein Zeiger auf eine Konstante befindet. Konstanz schränkt die Lebensdauer nicht ein; const-Werte in C ++ können weiterhin nicht mehr vorhanden sein.
"nicht einmal den Behälter im Schleifenkörper freilegen" was meinst du?
Dani
2
@Dani: Nun, kontrastiere dies mit der Konstruktion aus dem 20. Jahrhundert for (int i = 0; i < v.size(); i++). Hier müssen wir v[i]innerhalb der Schleife sagen , dh wir müssen den Container explizit erwähnen. Die RBFL hingegen führt die Schleifenvariable ein, die direkt als Wert verwendet werden kann, sodass keine Kenntnis des Containers innerhalb der Schleife erforderlich ist. Dies ist ein Hinweis auf die beabsichtigte Verwendung der RBFL für Schleifen, die nichts über den Container wissen müssen. Löschen ist die genau entgegengesetzte Situation, in der es nur um den Container geht.
Kerrek SB
3
@skyhisi: In der Tat. Dies ist eine der legitimen Verwendungen des Post-Inkrements: Erstes Inkrement it, um den nächsten gültigen Iterator abzurufen und dann den alten zu löschen. Umgekehrt funktioniert es nicht!
Kerrek SB
5
Ich habe irgendwo gelesen, dass in C ++ 11 it = v.erase(it);jetzt auch für Maps funktioniert. Das heißt, erase () für alle assoziativen Elemente gibt jetzt den nächsten Iterator zurück. Der alte Kludge, der ein Nachinkrement ++ innerhalb von delete () erforderte, wird also nicht mehr benötigt. Dies ist (falls zutreffend) eine gute Sache, da sich der Kludge auf die Magie des überschriebenen Post-Inkrements innerhalb eines Funktionsaufrufs stützte, die von neuen Betreuern "behoben" wurde, um das Inkrement aus dem Funktionsaufruf zu entfernen oder es auszutauschen zu einer Vorinkrementierung "weil das nur eine Stilsache ist" usw.
Dewi Morgan
3
warum sollten Sie it++die ifundelse Blöcke anrufen ? wäre es nicht genug, es zu nennen einmal nach diesen?
Nburk
25
Ich persönlich bevorzuge dieses Muster, das auf Kosten einer zusätzlichen Variablen etwas klarer und einfacher ist:
for(auto it = m.cbegin(), next_it = it; it != m.cend(); it = next_it){++next_it;if(must_delete){
m.erase(it);}}
Vorteile dieses Ansatzes:
Der for-Schleifen-Inkrementor ist als Inkrementor sinnvoll.
Die Löschoperation ist eine einfache Löschoperation, anstatt mit der Inkrementlogik gemischt zu werden.
Nach der ersten Zeile des Schleifenkörpers wird die Bedeutung von itund next_itbleibt während der gesamten Iteration festgelegt, sodass Sie problemlos zusätzliche Anweisungen hinzufügen können, die auf sie verweisen, ohne darüber nachzudenken, ob sie wie beabsichtigt funktionieren (außer natürlich, dass Sie sie itnach dem Löschen nicht verwenden können). .
Ich kann mir tatsächlich einen weiteren Vorteil vorstellen: Wenn die Schleife Code aufruft, der den iterierten oder vorherigen Eintrag löscht (und die Schleife ist sich dessen nicht bewusst), funktioniert sie ohne Schaden. Die einzige Einschränkung besteht darin, ob etwas löscht, worauf next_it oder Nachfolger hinweisen. Eine vollständig gelöschte Liste / Karte kann ebenfalls getestet werden.
Larswad
Diese Antwort ist einfach und klar, selbst wenn die Schleife komplexer ist und über mehrere Logikebenen verfügt, um zu entscheiden, ob sie gelöscht oder andere verschiedene Aufgaben ausgeführt werden sollen. Ich habe jedoch eine Bearbeitung vorgeschlagen, um es etwas einfacher zu machen. "next_it" kann im for's init auf "it" gesetzt werden, um Tippfehler zu vermeiden. Da die Anweisungen init und iteration es und next_it auf dieselben Werte setzen, müssen Sie nicht "next_it = it" sagen. am Anfang der Schleife.
CDgraham
1
Denken Sie an alle, die diese Antwort verwenden: Sie müssen "++ next_it" in der for-Schleife und nicht im Iterationsausdruck haben. Wenn Sie versuchen, es als "it = next_it ++" in den Iterationsausdruck zu verschieben, versuchen Sie bei der letzten Iteration, wenn "it" gleich "m.cend ()" gesetzt wird, "next_it" zu iterieren. nach "m.cend ()", was fehlerhaft ist.
CDgraham
6
Kurz gesagt: "Wie entferne ich eine Karte, während ich sie iteriere?"
Mit alter Karte impl: Sie können nicht
Mit neuer Karte impl: fast wie @KerrekSB vorgeschlagen. Aber es gibt einige Syntaxprobleme in dem, was er gepostet hat.
Aus der GCC-Karte impl (Hinweis 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
Beispiel mit altem und neuem Stil:
#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;}
Drucke:
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.
Ich verstehe es nicht Womit liegt das Problem mi.erase(it++);?
lvella
1
@ lvella siehe op. "Wenn ich map.erase verwende, werden die Iteratoren ungültig".
Kashyap
Ihre neue Methode funktioniert nicht, wenn die Karte nach dem Löschen leer wird. In diesem Fall wird der Iterator ungültig. Kurz nach dem Löschen ist es also besser, sie einzufügen if(mi.empty()) break;.
Rahat Zaman
4
Der C ++ 20-Entwurf enthält die Komfortfunktion std::erase_if.
Sie können diese Funktion also als Einzeiler verwenden.
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);});
Ziemlich traurig, oder? Normalerweise baue ich einen Container mit Iteratoren auf, anstatt ihn während des Durchlaufs zu löschen. Durchlaufen Sie dann den Container und verwenden Sie 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)}
Antworten:
Die Standardsprache zum Löschen von assoziativen Containern:
Beachten Sie, dass wir hier wirklich eine gewöhnliche
for
Schleife wollen , da wir den Container selbst modifizieren. Die bereichsbasierte Schleife sollte strikt für Situationen reserviert werden, in denen wir uns nur um die Elemente kümmern. Die Syntax für die RBFL macht dies deutlich, indem der Container nicht einmal innerhalb des Schleifenkörpers verfügbar gemacht wird.Bearbeiten. Vor C ++ 11 konnten Sie Konstanteniteratoren nicht löschen. Dort müsste man sagen:
Das Löschen eines Elements aus einem Container steht nicht im Widerspruch zur Konstanz des Elements. In Analogie dazu war es immer absolut legitim,
delete p
wop
sich ein Zeiger auf eine Konstante befindet. Konstanz schränkt die Lebensdauer nicht ein; const-Werte in C ++ können weiterhin nicht mehr vorhanden sein.quelle
for (int i = 0; i < v.size(); i++)
. Hier müssen wirv[i]
innerhalb der Schleife sagen , dh wir müssen den Container explizit erwähnen. Die RBFL hingegen führt die Schleifenvariable ein, die direkt als Wert verwendet werden kann, sodass keine Kenntnis des Containers innerhalb der Schleife erforderlich ist. Dies ist ein Hinweis auf die beabsichtigte Verwendung der RBFL für Schleifen, die nichts über den Container wissen müssen. Löschen ist die genau entgegengesetzte Situation, in der es nur um den Container geht.it
, um den nächsten gültigen Iterator abzurufen und dann den alten zu löschen. Umgekehrt funktioniert es nicht!it = v.erase(it);
jetzt auch für Maps funktioniert. Das heißt, erase () für alle assoziativen Elemente gibt jetzt den nächsten Iterator zurück. Der alte Kludge, der ein Nachinkrement ++ innerhalb von delete () erforderte, wird also nicht mehr benötigt. Dies ist (falls zutreffend) eine gute Sache, da sich der Kludge auf die Magie des überschriebenen Post-Inkrements innerhalb eines Funktionsaufrufs stützte, die von neuen Betreuern "behoben" wurde, um das Inkrement aus dem Funktionsaufruf zu entfernen oder es auszutauschen zu einer Vorinkrementierung "weil das nur eine Stilsache ist" usw.it++
dieif
undelse
Blöcke anrufen ? wäre es nicht genug, es zu nennen einmal nach diesen?Ich persönlich bevorzuge dieses Muster, das auf Kosten einer zusätzlichen Variablen etwas klarer und einfacher ist:
Vorteile dieses Ansatzes:
it
undnext_it
bleibt während der gesamten Iteration festgelegt, sodass Sie problemlos zusätzliche Anweisungen hinzufügen können, die auf sie verweisen, ohne darüber nachzudenken, ob sie wie beabsichtigt funktionieren (außer natürlich, dass Sie sieit
nach dem Löschen nicht verwenden können). .quelle
Kurz gesagt: "Wie entferne ich eine Karte, während ich sie iteriere?"
Aus der GCC-Karte impl (Hinweis GXX_EXPERIMENTAL_CXX0X ):
Beispiel mit altem und neuem Stil:
Drucke:
quelle
mi.erase(it++);
?if(mi.empty()) break;
.Der C ++ 20-Entwurf enthält die Komfortfunktion
std::erase_if
.Sie können diese Funktion also als Einzeiler verwenden.
quelle
Ziemlich traurig, oder? Normalerweise baue ich einen Container mit Iteratoren auf, anstatt ihn während des Durchlaufs zu löschen. Durchlaufen Sie dann den Container und verwenden Sie map.erase ()
quelle
Unter der Annahme von C ++ 11 handelt es sich hier um einen einzeiligen Schleifenkörper, wenn dies mit Ihrem Programmierstil übereinstimmt:
Ein paar andere kleine Stiländerungen:
Map::const_iterator
) an, wenn dies möglich / zweckmäßig istauto
.using
für Vorlagentypen, um dasMap::const_iterator
Lesen / Verwalten von Hilfstypen ( ) zu vereinfachen.quelle