Wie entferne ich aus einer Karte, während ich sie iteriere? wie:
std::map<K, V> map;
for(auto i : map)
if(needs_removing(i))
// remove it from the map
Wenn ich benutze map.erase
es wird die Iteratoren ungültig machen
Daniel
Wie entferne ich aus einer Karte, während ich sie iteriere? wie:
std::map<K, V> map;
for(auto i : map)
if(needs_removing(i))
// remove it from the map
Wenn ich benutze map.erase
es wird die Iteratoren ungültig machen
Kerrek SB
Das standardmäßige 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 wirklich ein gewöhnliches wollen for
Schleife hier, da wir den Container selbst ändern. Die bereichsbasierte Schleife sollte ausschließlich Situationen vorbehalten sein, in denen wir uns nur um die Elemente kümmern. Die Syntax für die RBFL macht dies deutlich, indem nicht einmal der Container innerhalb des Schleifenkörpers exponiert wird.
Bearbeiten. Vor C++11 konnten Sie Konstantiteratoren nicht löschen. Da 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. Analog dazu war es schon immer vollkommen legitim delete p
wo p
ist ein Zeiger auf eine Konstante. Beständigkeit schränkt die Lebensdauer nicht ein; const-Werte in C++ können immer noch aufhören zu existieren.
“Nicht einmal den Behälter im Schleifenkörper freilegen”, was meinst du?
– Dani
22. November 2011 um 22:34 Uhr
@Dani: Nun, kontrastieren Sie dies mit dem Bau des 20. Jahrhunderts for (int i = 0; i < v.size(); i++)
. Hier müssen wir sagen v[i]
innerhalb der Schleife, 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 innerhalb der Schleife keine Kenntnis des Containers erforderlich ist. Dies ist ein Hinweis auf die beabsichtigte Verwendung des RBFL für Schleifen, die dies tun nicht muss über den Container Bescheid wissen. Löschen ist die komplette entgegengesetzte Situation, wo es nur um den Container geht.
– Kerrek SB
22. November 2011 um 22:41 Uhr
@skyhisi: In der Tat. Dies ist eine der legitimen Verwendungen des Post-Inkrements: Zuerst Zuwachs it
um den nächsten gültigen Iterator zu erhalten, und dann das alte löschen. Umgekehrt geht es nicht!
– Kerrek SB
22. November 2011 um 22:55 Uhr
Ich habe irgendwo gelesen, dass in C++11, it = v.erase(it);
Funktioniert jetzt auch für Maps. Das heißt, erase() an alle assoziative Elemente geben jetzt den nächsten Iterator zurück. Der alte Kludge, der ein Post-Increment++ innerhalb von delete() erforderte, wird also nicht mehr benötigt. Dies ist (falls wahr) eine gute Sache, da sich der Kludge auf die Magie des überschriebenen Post-Inkrements innerhalb eines Funktionsaufrufs stützte, die von Neulingen “behoben” wurde, um das Inkrement aus dem Funktionsaufruf zu entfernen oder es auszutauschen zu einem Vorinkrement “weil das nur eine Stilsache ist” usw.
– Dewi Morgan
29. Januar 2015 um 23:11 Uhr
warum würdest du anrufen it++
in dem if
und else
Blöcke? würde es nicht reichen, es einmal anzurufen nach dem diese?
– nburk
14. Mai 2015 um 15:21 Uhr
D Coetzee
Ich persönlich bevorzuge dieses Muster, das etwas klarer und einfacher ist, auf Kosten einer zusätzlichen Variablen:
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:
it
und next_it
bleiben während der gesamten Iteration unverändert, sodass Sie problemlos zusätzliche Anweisungen hinzufügen können, die sich auf sie beziehen, ohne sich darüber den Kopf zu zerbrechen, ob sie wie beabsichtigt funktionieren (außer natürlich, dass Sie sie nicht verwenden können). it
nach dem Löschen).Ich kann mir tatsächlich einen weiteren Vorteil vorstellen, wenn die Schleife Code aufruft, der diesen Eintrag löscht, der über oder frühere iteriert wird (und die Schleife sich dessen nicht bewusst ist), funktioniert es ohne Schaden. Die einzige Einschränkung besteht darin, ob etwas löscht, worauf next_it oder Nachfolger zeigen. Eine vollständig gelöschte Liste/Map kann ebenfalls getestet werden.
– Larswad
21. November 2017 um 15:13 Uhr
Diese Antwort ist einfach und klar, auch wenn die Schleife komplexer ist und über mehrere Logikebenen verfügt, um zu entscheiden, ob gelöscht oder andere Aufgaben ausgeführt werden sollen. Ich habe jedoch eine Bearbeitung vorgeschlagen, um es ein wenig einfacher zu machen. “next_it” kann in der for-Initialisierung auf “it” gesetzt werden, um Tippfehler zu vermeiden, und da die init- und iteration-Anweisungen sowohl it als auch next_it auf dieselben Werte setzen, brauchen Sie nicht “next_it = it;” zu sagen. am Anfang der Schleife.
– cdgraham
7. November 2018 um 19:06 Uhr
Denken Sie an alle, die diese Antwort verwenden: Sie müssen “++next_it” innerhalb 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. Vergangenheit “m.cend()”, was fehlerhaft ist.
– cdgraham
7. November 2018 um 19:14 Uhr
Ich sehe alle Vorteile als Nachteile.
– Lukas Salich
19. Dezember 2021 um 5:07 Uhr
Unter der Annahme von C ++ 11 ist hier ein einzeiliger Schleifenkörper, wenn dies mit Ihrem Programmierstil übereinstimmt:
using Map = std::map<K,V>;
Map map;
// Erase members that satisfy needs_removing(itr)
for (Map::const_iterator itr = map.cbegin() ; itr != map.cend() ; )
itr = needs_removing(itr) ? map.erase(itr) : std::next(itr);
Ein paar andere kleinere Stiländerungen:
Map::const_iterator
) wenn möglich/bequem, über die Verwendung auto
.using
für Vorlagentypen, um Hilfstypen zu erstellen (Map::const_iterator
) leichter zu lesen/pflegen.Kurz gesagt: “Wie entferne ich aus einer Karte, während ich sie iteriere?”
Von der GCC-Karte impl (Anm 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>
using namespace std;
typedef map<int, int> t_myMap;
typedef vector<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;});
return 0;
}
Drucke:
main() ENTRY
Init
1-1
2-1
3-1
4-1
5-1
6-1
after old style erase of 3 & 4..
1-1
2-1
5-1
6-1
after new style erase of 5
1-1
2-1
6-1
Process returned 0 (0x0) execution time : 0.021 s
Press any key to continue.
PeterT
Der C++20-Entwurf enthält die Convenience-Funktion 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);});
Michael Daum
Ziemlich traurig, oder? Normalerweise baue ich einen Container mit Iteratoren auf, anstatt sie während der Traversierung 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)
}
Ähnlich: stackoverflow.com/questions/1038708/…
– Christian Ammer
22. November 2011 um 22:37 Uhr
Noch ähnlicher: stackoverflow.com/questions/800955/…
– Kleist
22. November 2011 um 22:47 Uhr
Und: stackoverflow.com/questions/180516/…
– Christian Ammer
22. November 2011 um 22:54 Uhr
Zum gleichen Thema: stackoverflow.com/questions/263945/…
– Agostino
1. März 2015 um 2:15 Uhr