2009-06-13 6 views
25

Ich habe einen Vektor, der Zeiger auf viele dynamisch instanziierte Objekte speichert, und ich versuche, durch den Vektor zu iterieren und bestimmte Elemente zu entfernen (aus Vektor entfernen und Objekt zu zerstören), aber ich habe Probleme. Hier ist, wie es aussieht:Wie löschen und löschen Sie Zeiger auf Objekte in einem Vektor gespeichert?

vector<Entity*> Entities; 
    /* Fill vector here */ 
    vector<Entity*>::iterator it; 
    for(it=Entities.begin(); it!=Entities.end(); it++) 
     if((*it)->getXPos() > 1.5f) 
      Entities.erase(it); 

Wenn eine der Entity-Objekte xPos bekommen> 1.5, das Programm abstürzt mit einer Assertion Fehler ... Wer weiß, was ich falsch mache?

Ich bin mit VC++ 2008

+0

Bitte kennzeichnen Sie Ihre Fragen mit der Sprache/Umgebung, die Sie verwenden, damit wir wissen, was Sie auf der Hauptseite verwenden (und Sie werden viele weitere Ansichten erhalten). – Zifre

+0

Aber du weißt nicht, was er mit dem Vektor im Rest seines Codes macht! Im Allgemeinen sollte ein Vektor der erste Wahlcontainer sein, wobei andere Dinge gleich sind. –

+2

Im Allgemeinen sollten Sie STL-Algorithmen handgeschriebenen Schleifen vorziehen. – rlbond

Antwort

37

Sie müssen vorsichtig sein, da erase() vorhandene Iteratoren ungültig macht. Allerdings ir liefert ein neuer gültiger Iterator Sie verwenden können:

for (it = Entities.begin(); it != Entities.end();) 
    if((*it)->getXPos() > 1.5f) 
     delete * it; 
     it = Entities.erase(it); 
    } 
    else { 
     ++it; 
    } 
} 
+0

Scheint zu funktionieren, obwohl ich mich frage, was ist der Unterschied zwischen dieser und Keand64's Antwort? vector :: erase() gibt an, den Destruktor des Objekts aufzurufen, also ist "delete * it;" notwendig? –

+4

Zeiger haben keine Destruktoren. Der Destruktor für das Ding im Vektor würde nur aufgerufen werden, wenn es sich um eine Sammlung von Entity-Werten handelt. Daher ist der Aufruf zum Löschen wichtig, wenn Sie ein Speicherleck vermeiden möchten. –

+1

@Gman Ich denke du meinst den Iterator, nicht den Zeiger. –

0

Sobald Sie den Vektor ändern, werden alle ausstehenden Iteratoren ungültig. Mit anderen Worten, Sie können den Vektor nicht ändern, während Sie ihn durchlaufen. Denken Sie darüber nach, was das für die Erinnerung bedeutet, und Sie werden sehen, warum. Ich vermute, dass Ihre Assert eine Asse "ungültiger Iterator" ist.

std :: vector :: erase() gibt einen Iterator zurück, den Sie verwenden sollten, um den von Ihnen verwendeten zu ersetzen. Siehe here.

+0

löscht tatsächlich nur Iteratoren, die auf das gelöschte Element und die Elemente danach zeigen, Iteratoren, die auf niedrigere Elemente zeigen, werden nicht ungültig gemacht. – Dolphin

2
if((*it)->getXPos() > 1.5f) 
{ 
    delete *it; 
    it = Entities.erase(it); 
} 
+2

Das ist falsch, weil der Iterator, der von Erase() zurückgegeben wird, nach dem Löschen inkrementiert wird. –

8

Die „richtige“ Weg, dies zu tun, wird ein Algorithmus verwendet:

#include <algorithm> 
#include <functional> 

// this is a function object to delete a pointer matching our criteria. 
struct entity_deleter 
{ 
    void operator()(Entity*& e) // important to take pointer by reference! 
    { 
     if (e->GetXPos() > 1.5f) 
     { 
      delete e; 
      e = NULL; 
     } 
} 

// now, apply entity_deleter to each element, remove the elements that were deleted, 
// and erase them from the vector 
for_each(Entities.begin(), Entities.end(), entity_deleter()); 
vector<Entity*>::iterator new_end = remove(Entities.begin(), Entities.end(), static_cast<Entity*>(NULL)); 
Entities.erase(new_end, Entities.end()); 

Jetzt weiß ich, was du denkst. Sie denken, dass einige der anderen Antworten kürzer sind. Aber (1) diese Methode kompiliert in der Regel zu schneller Code - versuchen Sie es zu vergleichen, (2) dies ist die "richtige" STL Weg, (3) gibt es weniger eine Chance für dumme Fehler, und (4) es ist einfacher zu Lesen Sie, sobald Sie den STL-Code lesen können. Es lohnt sich, die STL-Programmierung zu lernen, und ich schlage vor, Sie schauen sich Scott Meyers grossartiges Buch "Effective STL" an, das viele STL-Tipps zu solchen Sachen enthält.

Ein weiterer wichtiger Punkt ist, dass die Elemente nicht bis zum Ende der Operation gelöscht werden müssen, da die Elemente nicht gelöscht werden müssen. GMan schlug vor, eine Liste zu verwenden, um dies zu vermeiden, aber mit dieser Methode ist die gesamte Operation O (n). Der obige Code von Neil ist dagegen O (n^2), da die Suche O (n) ist und die Entfernung O (n) ist.

+0

Ich wurde nicht gemacht, und schrieb, dass der Code auf der Oberseite ist, wenn die Zeiger nicht gelöscht werden müssen. Manchmal möchten Sie einen Container mit Zeigern mit gemeinsamem Besitz. – rlbond

+4

Deutlich weniger klar als die explizite Schleife, IMHO. –

+0

Und es ist mehr Code auch! –

0

Das Hauptproblem besteht darin, dass die meisten STL-Container-Iteratoren das Hinzufügen oder Entfernen von Elementen zum Container nicht unterstützen. Einige werden alle Iteratoren ungültig machen, andere werden nur einen Iterator ungültig machen, der auf ein Element zeigt, das entfernt wird. Bis Sie ein besseres Gefühl dafür bekommen, wie jeder der Container funktioniert, müssen Sie vorsichtig sein, die Dokumentation darüber zu lesen, was Sie mit einem Container tun können und was nicht.

STL-Container erzwingen keine bestimmte Implementierung, aber ein Vektor wird normalerweise von einem Array unter der Haube implementiert. Wenn Sie ein Element am Anfang entfernen, werden alle anderen Elemente verschoben. Wenn Sie einen Iterator haben, der auf eines der anderen Elemente zeigt, zeigt es möglicherweise auf das Element nach dem alten Element. Wenn Sie ein Element hinzufügen, muss das Array möglicherweise in der Größe angepasst werden, so dass ein neues Array erstellt wird, das alte Material kopiert wird und der Iterator nun auf die alte Version des Vektors zeigt, was schlecht ist.

Für Ihr Problem sollte es sicher sein, durch den Vektor rückwärts iterieren und Elemente entfernen, wie Sie gehen. Es wird auch etwas schneller sein, da Sie sich nicht um Gegenstände bewegen werden, die Sie später löschen werden.

vector<Entity*> Entities; 
/* Fill vector here */ 
vector<Entity*>::iterator it; 
for(it=Entities.end(); it!=Entities.begin();){ 
    --it; 
    if(*(*it) > 1.5f){ 
    delete *it; 
    it=Entities.erase(it); 
    } 
} 
+0

Während die Rückwärts-Iteration schlau ist, werden Sie zwei Probleme haben. Erstens, vector :: erase() macht alle Iteratoren ungültig, daher verwendet der obige Code einen ungültigen Iterator. Aber das drängendere Problem ist, dass vector :: Erase() einen reverse_iterator nicht akzeptiert! Der Code, den Sie oben geschrieben haben, sollte nicht kompiliert werden. – rlbond

+0

hmmm, löschen nicht einen reverse_iterator nehmen könnte ein Problem sein :) Aber löscht nicht Iteratoren auf Elemente vor dem Element gelöscht ungültig. So oder so, behoben mit kompilierendem und funktionierendem Code. – Dolphin