C++ Objekte aus Vector löschen, während der Iterator duch den Vector geht

T

Tobi86

Gast
Guten Morgen,
ich möchte ein Objekt nach Bedingungsprüfung aus einem Vector löschen und danach das Speicherloch schließen. Mein Wunschtraum-Code sieht wie folgt aus, damit ihr wisst, was ich meine:
Code:
//Vector erstellen, der eine Klasse beinhaltet
vector<Enemy> GegnerV;

//Vector befüllen
GegnerV.push_back(Enemy(blablablub));

//Alle Gegner auf Tod prüfen
for (i=GegnerV.begin();i<GegnerV.end();i++)
{
    if ( i->current_hp <= 0)
    {
        cout << i->name << " ist tot.";
        GegnerV.erase(i);
        cout << " Und wird rausgeschmissen." << endl;
    }
}
Wenn ich aber mitten im Durchgang des Iterators ein Objekt lösche, gibt es wohl ein Problem mit einem Speicherloch. Wie kann ich dieses fehlerfrei schließen?

Vielen Dank vorab für Hinweise!
 
arbeite lieber mit eine kopie von vektor<enemy>. Wenn du während des itterierens versuchst einen objekt zu löschen hast du sicher ganz schnell eine exception.


Code:
GegnerV.push_back(Enemy(blablablub));
vector<Enemy> copy = GegnerV->Clone(); //oder so ähnlich...

for (i=copy.begin();i<copy.end();i++)
{
    if ( i->current_hp <= 0)
    {
        cout << i->name << " ist tot.";
        GegnerV.erase(i);
        cout << " Und wird rausgeschmissen." << endl;
    }
}

Da du jetzt nicht direkt am GegnerV itterierst aber mit eine gültige kopie arbeitest, soll es kein Problem mehr sein den Gegner zu löschen.

Bitte beachte, wenn ein anderer Thread mit GegnerV arbeiten sollte (was nicht in diesen Beispiel aufgeführt ist) sollte man diese Methode überdenken, da i bereits schon gelöscht sein könnte
 
Vectoren machen viele Sachen automatisch. So weit ich weiss müssten die nachfolgenden Elemente einfach einen neuen Platz bekommen (rutschen runter). Es bleibt kein Loch übrig.

http://www.cplusplus.com/reference/stl/vector/erase/

edit:
Ja Roker hat Recht. Dann hätte man aber noch das Problem, dass der Nachfolger des gelöschten Objekts übersprungen werden würde, weils eine Position runter gerutscht ist.
 
Zuletzt bearbeitet:
ähm ich dachte er will nur einen Objekt löschen! Wozu willst du den einen Loch haben, wenn ich fragen darf! Wenn du wirklich einen Loch haben willst dann lösche das Objekt nicht raus sondern referenziere es mit NULL!
 
@roker002: "Speicherloch" war falsch. Aber der Iterator kommt wohl durcheinander - jedenfalls bekomme ich einen Laufzeitfehler, wenn ich ein .erase durchführe und der Iterator dann das nächste Objekt ansteuern will.

Code:
vector<Enemy> copy = GegnerV->Clone(); //oder so ähnlich...
funktioniert nicht. Ein clone kennen Vectoren wohl nicht...

\main.cpp(252): error C2819: Der Typ 'std::vector<_Ty>' hat keinen überladenen Elementoperator '->'
1> with
1> [
1> _Ty=Enemy
1> ]
1> Wollten Sie stattdessen '.' verwenden?
\main.cpp(252): error C2039: 'Clone': Ist kein Element von 'std::vector<_Ty>'

Auch GegnerV.Clone() ist nicht. GIbts noch mehr Kopier-Operatoren?
 
für diese Aufgabe ist remove_if sehr gut geeignet. Dafür ist ein Prädikat erforderlich, welches die zu löschenden Enemies erkennt. In C++11 ist das Elegant mit einer Lambda Funktion machbar, ansonsten schreibt man einfach einen Funktor.

Code:
struct IsEnemyDead {
   bool operator()(const Enemy& e) const {  
        return e,current_hp <= 0;
   }
};


...

   GegnerV.erase(
      std::remove_if(
           GegnerV.begin(), GegnerV.end(), 
           IsEnemyDead()), 
      GegnerV.end()
    );


Die Lambda-Funktion-Variante sieht etwa so aus

Code:
   GegnerV.erase(
      std::remove_if(
          GegnerV.begin(), GegnerV.end(), 
          [](const Enemy& e) {return e.current_hp <= 0;}
      ), 
      GegnerV.end()
   );
 
Zuletzt bearbeitet: (bug)
Ich weiß jetzt nicht genau, da ich nicht so sehr mit der STL programmiere, aber müßte es nicht
Code:
for (i=copy.begin();i!=copy.end();i++)
heißen?

Zum Thema: Ich würde in so einer Situation das Array von hinten durchlaufen und Elemente entfernen, so verschieben sich die noch zu überprüfenden Elemente vorher im Vektor nicht im Speicher.
Alternativ kann man auch einen Index statt eines Iterators nehmen (beim Vektor) und alle Indices, die gelöscht werden sollen, in einen zweiten Vektor eintragen und im Anschluss diesen benutzend löschen. Dabei wieder von hinten durchlaufen!

P.S.: Die Methode von convexus ist noch schöner! ;-)
 
convexus schrieb:
Code:
struct IsEnemyDead {
   bool operator()(const Enemy& e) const {  
        return e,current_hp <= 0;
   }
};

...

   GegnerV.erase(
      std::remove_if(
           GegnerV.begin(), GegnerV.end(), 
           IsEnemyDead()), 
      GegnerV.end()
    );

Oha, da steige ich nicht ganz durch, sorry. Die Funktion IsEnemyDead bereitet mir
Kopfschmerzen. Und auch die Syntax des unteren Teils. Kannst Du das nochmal
ausführlicher kommentieren oder mir einen Hinweis geben, wo ich dazu
Referenzen / Lernmaterial finde?
 
@tobi
clone war nur so eine idee. Versuch doch mal selbst nach ähnlichen Methoden zu suchen! Hier gibt es meist Pseudo Code (keinen echten Code), daher ist es in den meisten Fällen unbrauchbar. Man sollte auch selbst was lernen können und nicht nur abschreiben ;)
 
Oha, da steige ich nicht ganz durch, sorry. Die Funktion IsEnemyDead bereitet mir
Kopfschmerzen. Und auch die Syntax des unteren Teils. Kannst Du das nochmal
ausführlicher kommentieren oder mir einen Hinweis geben, wo ich dazu
Referenzen / Lernmaterial finde?

IsEnemyDead() ist ein sogenanntes Funktionsobjekt also eine Instanz der Klasse/ Strukts IsEnemyDead. Wegen der Überladung des ()-Operators können Instanzen davon wie Funktionen verwendet werden. Beispiel
Code:
   Enemy e(...); // Enemy e definieren

   IsEnemyDead()(e); // führt die Methode  IsEnemyDead::operator() für das Argument e aus
                               // IsEnemyDead() ist ein Konstruktor-Aufruf und erzeugt ein Objekt

   // mit expliziten Objekt
   IsEnemyDead pred; 
   if(pred(e)) {  
      std::cout << "Enemy is dead\n";
   }

Die Funktion remove_if ist Teil der Standardbibliothek und gehört damit zu jedem standardkonformen C++-Compiler. Hier kannst du Näheres zu remove_if finden http://www.cplusplus.com/reference/algorithm/remove_if
 
Tobi86 schrieb:
Guten Morgen,
ich möchte ein Objekt nach Bedingungsprüfung aus einem Vector löschen und danach das Speicherloch schließen. Mein Wunschtraum-Code sieht wie folgt aus, damit ihr wisst, was ich meine:
Code:
//Vector erstellen, der eine Klasse beinhaltet
vector<Enemy> GegnerV;

//Vector befüllen
GegnerV.push_back(Enemy(blablablub));

//Alle Gegner auf Tod prüfen
for (i=GegnerV.begin();i<GegnerV.end();i++)
{
    if ( i->current_hp <= 0)
    {
        cout << i->name << " ist tot.";
        GegnerV.erase(i);
        cout << " Und wird rausgeschmissen." << endl;
    }
}
Wenn ich aber mitten im Durchgang des Iterators ein Objekt lösche, gibt es wohl ein Problem mit einem Speicherloch. Wie kann ich dieses fehlerfrei schließen?

Vielen Dank vorab für Hinweise!

Convexus hat dir ja bereits geholfen. Trotzdem möchte ich an dieser Stelle noch ein paar Hinweise / Ratschläge bezüglich deiner ursprünglichen Lösung anbringen.

Das Entfernen eines Elements mit std::vector::erase() macht alle Iteratoren, die auf die entfernte oder nachfolgenden Positionen zeigen ungültig. Zum Glück gibt erase() aber seinerseits einen neuen Iterator zurück, den du dann zum Weiteriterieren benutzen kannst.

Code:
//Alle Gegner auf Tod prüfen
for ( i = GegnerV.begin(); i != GegnerV.end(); [COLOR="Red"]/* iterator wird im Rumpf erhöht */[/COLOR] )
{
	if ( i->current_hp <= 0)
	{
		cout << i->name << " ist tot.";
		[COLOR="Red"]i = GegnerV.erase( i );[/COLOR]
		cout << " Und wird rausgeschmissen." << endl;
	}
	else
	{
		[COLOR="Red"]++i;[/COLOR]
	}
}

Zu den "Speicherlöchern", das Entfernen eines Elements aus einem Vektor hinterläßt keine Löcher. Alle nachfolgenden Elemente rutschen einfach einen Platz nach unten. Da dieses Umkopieren der Elemente unter Umständen teuer sein kann**, ist das Entfernen aus Vektoren nur am Ende des Vektors effizient.

** Das hängt einfach davon ab, wieviel Aufwand mit dem Kopieren von Enemy-Objekten verbunden ist.
 
Zuletzt bearbeitet:
Vielen Dank euch allen, ich habe mir erstmal eine Funktion gebaut, die ich selber gut verstehe. Deine Version werde ich auch nochmal probieren, antred.

Alles andere schreit für mich noch nach "Lern mal weiter die Grundlagen" :D was ja auch soweit richtig ist und noch von mir angegangen wird (MUSS). Ich habe hier "C++ für Spieleprogrammierer" von Kalista, "Einstieg in C++" von Willemer sowie Video2Brains "Visual C++ 2008" und "C++ programmieren lernen" als Galileo Videokurs rumliegen. Jedoch habe ich das Gefühl, dass mir diese 4 Werke nicht helfen, z.B. convexus Codebeispiel komplett zu verstehen. Ich brauche wohl umfangreichere Literatur.

Kennt jemand Prinz's "C++ lernen und professionell anwenden"? Hat bei Amazon sehr gute Rezensionen (genau wie die Bücher, die ich schon habe :rolleyes: ), ist aber wohl deutlich umfangreicher. Oder habt ihr andere gute Literaturtipps? Preis egal...

Code:
//Alle Gegner auf Tod prüfen
bool tot = false;
for (i=GegnerV.begin();i<GegnerV.end();i++)
{
	if ( i->current_hp <= 0)
	{
		cout << i->name << " ist tot.";
		tot = true;
		toter = i->id - 2;
	}
}
if (tot)
{
	GegnerV.erase(GegnerV.begin()+toter);
}
tot = false;

EDIT: @antred: Dein Code ist super, Danke!
 
Zuletzt bearbeitet von einem Moderator:

Ähnliche Themen

Zurück
Oben