Ich muss einen Satz durchgehen und Elemente entfernen, die vordefinierte Kriterien erfüllen.
Dies ist der Testcode, den ich geschrieben habe:
#include <set>
#include <algorithm>
void printElement(int value) {
std::cout << value << " ";
}
int main() {
int initNum[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
std::set<int> numbers(initNum, initNum + 10);
// print '0 1 2 3 4 5 6 7 8 9'
std::for_each(numbers.begin(), numbers.end(), printElement);
std::set<int>::iterator it = numbers.begin();
// iterate through the set and erase all even numbers
for (; it != numbers.end(); ++it) {
int n = *it;
if (n % 2 == 0) {
// wouldn't invalidate the iterator?
numbers.erase(it);
}
}
// print '1 3 5 7 9'
std::for_each(numbers.begin(), numbers.end(), printElement);
return 0;
}
Zuerst dachte ich, dass das Löschen eines Elements aus der Menge während des Durchlaufens den Iterator ungültig machen würde und das Inkrement in der for-Schleife ein undefiniertes Verhalten hätte. Obwohl ich diesen Testcode ausgeführt habe und alles gut gelaufen ist, kann ich nicht erklären, warum.
Meine Frage: Ist dies das definierte Verhalten für Standardmengen oder ist diese Implementierung spezifisch? Ich benutze übrigens gcc 4.3.3 unter Ubuntu 10.04 (32-Bit-Version).
Vielen Dank!
Vorgeschlagene Lösung:
Ist dies eine korrekte Methode zum Iterieren und Löschen von Elementen aus der Menge?
while(it != numbers.end()) {
int n = *it;
if (n % 2 == 0) {
// post-increment operator returns a copy, then increment
numbers.erase(it++);
} else {
// pre-increment operator increments, then return
++it;
}
}
Bearbeiten: BEVORZUGTE LÖSUNG
Ich bin auf eine Lösung gekommen, die mir eleganter erscheint, obwohl sie genau das Gleiche tut.
while(it != numbers.end()) {
// copy the current iterator then increment it
std::set<int>::iterator current = it++;
int n = *current;
if (n % 2 == 0) {
// don't invalidate iterator it, because it is already
// pointing to the next element
numbers.erase(current);
}
}
Wenn innerhalb der Weile mehrere Testbedingungen vorliegen, muss jede den Iterator erhöhen. Ich mag diesen Code besser, weil der Iterator nur an einer Stelle inkrementiert wird , wodurch der Code weniger fehleranfällig und lesbarer wird.
++it
sollte etwas effizienter sein, alsit++
weil keine unsichtbare temporäre Kopie des Iterators verwendet werden muss. Kornels Version sorgt zwar länger dafür, dass die nicht gefilterten Elemente am effizientesten durchlaufen werden.Antworten:
Dies ist implementierungsabhängig:
Standard 23.1.2.8:
Vielleicht könnten Sie dies versuchen - dies ist standardkonform:
Beachten Sie, dass es sich bei ++ um ein Postfix handelt und daher die alte Position zum Löschen übergibt, jedoch aufgrund des Operators zuerst zu einer neueren Position springt.
Update 27.10.2015: C ++ 11 hat den Fehler behoben.
iterator erase (const_iterator position);
Geben Sie einen Iterator an das Element zurück, das auf das zuletzt entfernte Element folgt (oderset::end
wenn das letzte Element entfernt wurde). Der C ++ 11-Stil lautet also:quelle
deque
MSVC2013. Entweder ist ihre Implementierung fehlerhaft oder es gibt noch eine weitere Anforderung, die verhindert, dass dies funktioniertdeque
. Die STL-Spezifikation ist so kompliziert, dass Sie nicht erwarten können, dass alle Implementierungen ihr folgen, geschweige denn, dass Ihr gelegentlicher Programmierer sie sich merkt. STL ist ein Monster, das nicht zu zähmen ist, und da es keine eindeutige Implementierung gibt (und Testsuiten, falls vorhanden, offenbar nicht so offensichtliche Fälle wie das Löschen von Elementen in einer Schleife abdecken), ist STL ein glänzendes, sprödes Spielzeug, mit dem man mithalten kann ein Knall, wenn man es seitwärts betrachtet.Wenn Sie Ihr Programm über valgrind ausführen, werden eine Reihe von Lesefehlern angezeigt. Mit anderen Worten, ja, die Iteratoren werden ungültig, aber Sie haben Glück in Ihrem Beispiel (oder wirklich Pech, da Sie die negativen Auswirkungen von undefiniertem Verhalten nicht sehen). Eine Lösung hierfür besteht darin, einen temporären Iterator zu erstellen, die Temperatur zu erhöhen, den Zieliterator zu löschen und das Ziel dann auf die Temperatur festzulegen. Schreiben Sie Ihre Schleife beispielsweise wie folgt neu:
quelle
while
Schleife zu verwenden . dhfor ( ; it != numbers.end(); )
ist besser sichtbar mitwhile (it != numbers.end())
Sie verstehen falsch, was "undefiniertes Verhalten" bedeutet. Undefiniertes Verhalten bedeutet nicht "Wenn Sie dies tun, stürzt Ihr Programm ab oder führt zu unerwarteten Ergebnissen." Es bedeutet : „Wenn Sie das tun, Ihr Programm könnte abstürzen oder produzieren unerwartete Ergebnisse“, oder irgendetwas anderes tun, abhängig von Ihrem Compiler, Ihr Betriebssystem, die Phase des Mondes usw.
Wenn etwas ohne Absturz ausgeführt wird und sich so verhält, wie Sie es erwarten, ist dies kein Beweis dafür, dass es sich nicht um undefiniertes Verhalten handelt. Alles, was es beweist, ist, dass sein Verhalten zufällig für diesen bestimmten Lauf beobachtet wurde, nachdem es mit diesem bestimmten Compiler auf diesem bestimmten Betriebssystem kompiliert wurde.
Durch das Löschen eines Elements aus einer Menge wird der Iterator für das gelöschte Element ungültig. Die Verwendung eines ungültig gemachten Iterators ist ein undefiniertes Verhalten. Es ist einfach so passiert, dass das beobachtete Verhalten das war, was Sie in diesem speziellen Fall beabsichtigt haben; Dies bedeutet nicht, dass der Code korrekt ist.
quelle
if (n > 2 && n < 7 )
dann ändere, erhalte ich 0 1 2 4 7 8 9. - Das besondere Ergebnis hier hängt wahrscheinlich mehr von den Implementierungsdetails der Löschmethode und den festgelegten Iteratoren ab als von der Mondphase (nicht von dieser) sollte sich jemals auf Implementierungsdetails verlassen). ;)std::set::erase
die Rückgabe eines Iterators zulässt, damit Ihr MSVC-Code beim Kompilieren von gcc einen Knall bekommt" oder "Microsoft überprüft dies,std::bitset::operator[]
damit sich Ihr sorgfältig optimierter Bitset-Algorithmus auf a verlangsamt crawlen beim Kompilieren mit MSVC ". STL hat keine eindeutige Implementierung und seine Spezifikation ist ein exponentiell wachsendes, aufgeblähtes Durcheinander. Kein Wunder, dass das Löschen von Elementen aus einer Schleife das Fachwissen eines erfahrenen Programmierers erfordert ...Nur um zu warnen, dass im Fall eines Deque-Containers alle Lösungen, die prüfen, ob der Deque-Iterator gleich numbers.end () ist, unter gcc 4.8.4 wahrscheinlich fehlschlagen werden. Das Löschen eines Elements der Deque macht den Zeiger auf numbers.end () im Allgemeinen ungültig:
Ausgabe:
Beachten Sie, dass der Endzeiger auf dem Weg ungültig wurde, obwohl die Deque-Transformation in diesem speziellen Fall korrekt ist. Bei einer Deque einer anderen Größe ist der Fehler offensichtlicher:
Ausgabe:
Hier ist eine der Möglichkeiten, dies zu beheben:
quelle
do not trust an old remembered dq.end() value, always compare to a new call to dq.end()
.C ++ 20 hat "einheitliche Containerlöschung" und Sie können schreiben:
Und das funktioniert für
vector
,set
,deque
etc. Siehe cppReference für weitere Informationen.quelle
Dieses Verhalten ist implementierungsspezifisch. Um die Richtigkeit des Iterators zu gewährleisten, sollten Sie "it = numbers.erase (it);" verwenden. Anweisung, wenn Sie das Element löschen und in einem anderen Fall einfach den Iterator inerieren müssen.
quelle
Set<T>::erase
Version gibt keinen Iterator zurück.gcc 4.8
withc++1y
haben einen Fehler beim Löschen.it = collection.erase(it);
soll funktionieren, aber es kann sicherer seincollection.erase(it++);
Ich denke mit der STL-Methode '
remove_if
' von könnte helfen, ein seltsames Problem zu vermeiden, wenn versucht wird, das vom Iterator umschlossene Objekt zu löschen.Diese Lösung ist möglicherweise weniger effizient.
Nehmen wir an, wir haben eine Art Container wie einen Vektor oder eine Liste namens m_bullets:
'
it
' ist der Iterator, der 'remove_if
' zurückgibt, das dritte Argument ist eine Lambda-Funktion, die für jedes Element des Containers ausgeführt wird. Da der Container enthältBullet::Ptr
, muss die Lambda-Funktion diesen Typ (oder einen Verweis auf diesen Typ) als Argument übergeben.'
remove_if
' entfernt den Container, in dem die Lambda-Funktion true zurückgegeben hat, und verschiebt diesen Inhalt an den Anfang des Containers. Das 'it
' zeigt auf ein undefiniertes Objekt, das als Müll betrachtet werden kann. Objekte von 'it' bis m_bullets.end () können gelöscht werden, da sie Speicher belegen, aber Müll enthalten. Daher wird die Methode 'erase' für diesen Bereich aufgerufen.quelle
Ich bin auf dasselbe alte Problem gestoßen und fand den folgenden Code verständlicher, was in gewisser Weise den oben genannten Lösungen entspricht.
quelle