C++ Löschen von Objekten aus Vektor.

Dieser Teil hier:

Code:
for(SpriteIt = SpriteVector.begin();SpriteIt != SpriteVector.end();SpriteIt++)
{
	sf::Vector2f mousecoords(mMainWindow.mapPixelToCoords(sf::Vector2i(event.mouseButton.x, event.mouseButton.y)));
	
	if(SpriteIt->getGlobalBounds().contains(mousecoords))
	{
		SpriteVector.erase(std::remove_if(SpriteVector.begin(), SpriteVector.end(), IsEnemyDead()),SpriteVector.end());
	}
}

ist problematisch. Der Iterator, den du in der Schleife inkrementierst (übrigens, aus Effizienz-Gründen lieber Präinkrement nutzen ... also das ++ vor den Iterator setzen), machst du mit dem erase() eventuell ungültig. Wenn du unbedingt in einer Schleife erase() aufrufen möchtest, mußt du entweder sofort nach dem erase()-Aufruf die Schleife verlassen, oder den Rückgabewert von erase() nutzen. erase() gibt dir nämlich die Position des ersten Elements hinter dem eben gelöschten Bereich. Ich ändere den Code von oben mal entsprechend ab:

Code:
for(SpriteIt = SpriteVector.begin();SpriteIt != SpriteVector.end(); /* iterator incremented in loop body */ )
{
	sf::Vector2f mousecoords(mMainWindow.mapPixelToCoords(sf::Vector2i(event.mouseButton.x, event.mouseButton.y)));
	
	if(SpriteIt->getGlobalBounds().contains(mousecoords))
	{
		SpriteIt = SpriteVector.erase(std::remove_if(SpriteVector.begin(), SpriteVector.end(), IsEnemyDead()),SpriteVector.end());
	}
	else
	{
		++SpriteIt;
	}
}

Und NATÜRLICH entfernt dein Code nix aus dem vector! Dein IsEnemyDead-Funktor gibt ja auch einfach immer pauschal für jedes Element 0 (also false) zurück. :) Der Funktor muß für jedes Element, das entfernt werden soll, true liefern.
 
Ich weiß schon mal, dass wenn ich ihn auf 1 setze, alles gelöscht wird, so als hätte ich statt dem ganzen code, einfach nur:
Code:
SpriteVector.clear();
eingefügt :-)

Ich muss also etwas vor die remove_if Zeile einfügen, wie ich das richtig verstehe.
Ich weiß auch was ne Strukt ist, also auch sowas wie ne Klasse, nur dass alles "public", also von außen zugreifbar ist. Aber wie setzt man das "fallweise" auf true oder false. Bei einer bool wäre das ja noch einfacher.
In meinen schlauen Büchlein steht nichts zu dem Thema. Unter welchem Begriff müsste ich da überhaupt schauen. Kann nur irgendwie auf den Operator zugreifen, ohne was zu daran zu tun:
Code:
&IsEnemyDead::operator();
 
Zuletzt bearbeitet:
Klar, aber in deinem 1. Beitrag in diesem Thread tat dein IsEnemyDead-Funktor noch etwas, das zumindest so aussah, als könnte es das sein, was du eigentlich wolltest:

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

Wenn du das jetzt wieder verwendest und die Hinweise aus meinem letzten Beitrag beherzigst, dann sollte deinem Erfolg eigentlich nichts mehr im Wege stehen.
 
Ist das & nach dem Sprite eine Referenz?
Ich habe das bisher so gesehen, dass man das nach dem Datentyp oder der Klasse verwendet.
Sollte man dann statt:


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



So schreiben?:

Code:
 struct IsEnemyDead {
    bool operator() (const sf::Sprite& e) const {
    return e.current_hp <= 0;
    }
    };



Jetzt gibt es aber unter den "e"-Mitgliedern kein "current_hp" (daher hab ich das vorhin auch rausgelöscht).
 
Bronislaw schrieb:
Ist das & nach dem Sprite eine Referenz?
Ich habe das bisher so gesehen, dass man das nach dem Datentyp oder der Klasse verwendet.

So isses.

Bronislaw schrieb:
Sollte man dann statt:


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



So schreiben?:

Code:
 struct IsEnemyDead {
    bool operator() (const sf::Sprite& e) const {
    return e.current_hp <= 0;
    }
    };

Falls das Ding im namespace sf liegt (oder innerhalb einer Klasse / eines structs sf definiert ist) dann ja.


Bronislaw schrieb:
Jetzt gibt es aber unter den "e"-Mitgliedern kein "current_hp" (daher hab ich das vorhin auch rausgelöscht).

Na ja, irgend ein Attribut, an Hand dessen du entscheiden kannst, ob ein sf::Sprite-Objekt einen toten oder einen lebenden Feind darstellt, brauchst du natürlich, damit dein IsEnemyDead-Funktor seine Arbeit gescheit verrichten kann.
 
Also kein der sf::Sprite e members hat funktioniert. (sind lauter get oder set members) Sitze schon seit 2 Wochen daran und kann wegen dieses Problems keine Vektoren verwenden, obwohl die viel besser als Listen sind.

Vielleicht kann man das für einen Vektor überhaupt nicht programmieren? :-(
 
Ja wo zum Henker ist jetzt das Problem?? Wenn sf::Sprite keine public Daten-Member hat, dann nimm eben einen entsprechenden Getter!

Vielleicht kann man das für einen Vektor überhaupt nicht programmieren? :-(

Natürlich geht das.
 
vectoren sind DER standardcontainer überhaupt und super einfach so benutzen - wo ist denn überhaupt das Problem?

http://www.cplusplus.com/reference/vector/vector/erase/
Hast du dir das Beispiel mal angesehen wie einfach das ist? Und das Beispiel dort ist sogar noch kompliziert weil nicht einfach nur ein einziges Element gelöscht wird sondern ein Bereich (2 Iteratoren). Es geht aber auch einfach mit nur einem Iterator
Willst du evtl. nur sowas machen?:
Code:
for (SpriteIt = SpriteVector.begin(); SpriteIt != SpriteVector.end(); ++SpriteIt)
{
  if (SpriteIt->getGlobalBounds().contains(mousecoords))
  {
    SpriteVector.erase(SpriteIt);
  }
}

std::remove_if kann man benutzen aber musst du nicht, wenns dir eher Probleme macht.
Es ist dafür gedacht diese obige for-schleife nicht manuell hinschreiben zu müssen und das alles in 1 Zeile zu erledigen.
 
Zuletzt bearbeitet:
Was meinst Du mit Getter?


Erase will ich ja eben nicht verwenden, weil man damit nicht alle Einheiten löschen kann.

Hab wohl schon alle Lösungsmöglichkeiten im Netz zu dem Thema durchprobiert.....



Code:
     struct IsEnemyDead {
    bool operator() (const std::vector<sf::Sprite>& e) const {
    return e.size() <= 0;
    }
    };

Jetzt wird nichts mehr als Fehler angestrichen, dennoch Fehler beim Kompilieren.
 
Erase will ich ja eben nicht verwenden, weil man damit nicht alle Einheiten löschen kann.
Was meinst du damit? Selbstverständlich löscht erase aus dem Vector. Steht doch auch auf cplusplus.com
Ich glaube du machst es dir viel zu kompliziert und dein Problem ist nicht mehr als ein Einzeiler mit remove_if oder halt 3 Zeilen mit for, if if erase.

Wenn du in normaler Sprache einmal einfach sagst was du machen willst, könnte dir das jeder hier einfach hinschreiben und evtl verstehst du dann auch wo das Problem war...
Sowas in der Form
"Ich will alle Sprites überprüfen und wenn sie vom Mauscursor bedeckt sind UND die HP der Kreatur <0, dann soll es gelöscht werden".

Jetzt wird nichts mehr als Fehler angestrichen, dennoch Fehler beim Kompilieren.
Das ist ganz normal.
Wenn du irgendwo
Code:
abc;
hinschreibst wird die Entwiclungsumgebung wahrscheinlich auch keinen Fehler anzeigen aber der Compiler wird feststellen, dass er mit abc; nichts anfangen kann.

vectoren benutzt man wie jeden anderen Kontainer auch: man tut was rein, greift lesend auf Objekte zu und löscht sie wieder. Ob das nun eine liste, ein vector oder eine deque ist sollte für dich überhaupt keinen Unterschied ausmachen.
 
Zuletzt bearbeitet:
Mir geht es darum im Programm auf der Karte selbst erstellte Sprites wieder einzeln zu löschen, wenn ich mit rechter Maustaste darauf klicke. Erase löscht das auch einwandfrei, aber wenn die Elementezahl komplett auf 0 gesetzt wird, sprich auch das letzte Sprite auf der Karte per Mausklick gelöscht wird, dann wird der Iterator ungültig und das Programm schließt sich "vector iterator not incrementable" (der erste große Code-Block im ersten Beitrag dieses Fadens veranschaulicht diese Problematik), daher kann ich das nicht verwenden und muss stattdessen remove_if nehmen und das bereitet mir Probleme, das richtig hinzuschreiben.
 
Bronislaw schrieb:
Mir geht es darum im Programm auf der Karte selbst erstellte Sprites wieder einzeln zu löschen, wenn ich mit rechter Maustaste darauf klicke. Erase löscht das auch einwandfrei, aber wenn die Elementezahl komplett auf 0 gesetzt wird, sprich auch das letzte Sprite auf der Karte per Mausklick gelöscht wird, dann wird der Iterator ungültig und das Programm schließt sich "vector iterator not incrementable" (der erste große Code-Block im ersten Beitrag dieses Fadens veranschaulicht diese Problematik), daher kann ich das nicht verwenden und muss stattdessen remove_if nehmen und das bereitet mir Probleme, das richtig hinzuschreiben.

Ich glaube, du hast immer noch nicht verstanden, was erase() und remove_if() eigentlich tun - was ich merkwürdig finde, da du an früherer Stelle behauptet hast, du hättest die Referenz zu diesen Funktionen schon mehrmals studiert.

Willst du einen einzelnen Sprite löschen? Dann:

Code:
auto iteratorToFirstElementAfterDeletedElement = myVector.erase( iteratorToElementToBeRemoved );

Willst du ausnahmslos ALLE Sprites löschen? Dann:

Code:
myVector.clear();

Willst du nur Sprites löschen, die ein bestimmtes Kriterium erfüllen? Dann entweder:

Code:
bool shouldThisOneBeDeleted( const sf::Sprite& element )
{
	// Hier Code einfuegen, der das Kriterium für das uebergebene Element bewertet und dementspechend true oder false liefert.
	// return ...
}

for ( auto it = myVector.begin(); it != myVector.end(); /* iterator incremented in loop body */ )
{
	if ( shouldThisOneBeDeleted( *it ) )
	{
		it = myVector.erase( it );
	}
	else
	{
		++it;
	}
}

oder:

Code:
auto iteratorToFirstElementAfterDeletedBlock = myVector.erase( std::remove_if( myVector.begin(), myVector.end(), shouldThisOneBeDeleted ), myVector.end() );

Wenn du einen vector mit erase(), clear() oder so modifizierst, ist selbstverständlich immer darauf zu achten, daß bestehende Iteratoren dadurch eventuell ungültig gemacht werden. Wenn du z.B. innerhalb einer for-Schleife den vector mit clear() leerst, dann ist es doch logisch, daß du danach nicht weiter über den (nunmehr leeren) vector iterieren kannst, sondern die Schleife abbrechen mußt.
Ergänzung ()

P.S. Dir ist hoffentlich auch bewußt, daß remove_if() eigentlich gar nichts aus dem vector löscht sondern nur zu löschende Elemente so umsortiert, daß zu erhaltende Element im vector vorne liegen und zu löschende Elemente alle hinter dem letzten zu erhaltenden Element liegen. Dann gibt remove_if() dir einen Iterator auf den Beginn des Blocks zurück, in dem die zu löschenden Elemente liegen.
 
Zuletzt bearbeitet:
Mit Vektoren hab ich mich tatsächlich beschäftigt und da sogar mehrere kleine Programme geschrieben mit push_back, Elementausgabe, erase an verschiedenen Stellen und Bandbreitenlöschung von string Elementen usw. und da ging sogar das erase ohne Probleme, auch wenn die Vektorgröße dabei auf 0 gesetzt wurde.

Bin gerade dabei mir den Lösungsvorschlag anzusehen, aber erstmal vielen Dank soweit.
 
Bronislaw schrieb:
Mit Vektoren hab ich mich tatsächlich beschäftigt und da sogar mehrere kleine Programme geschrieben mit push_back, Elementausgabe, erase an verschiedenen Stellen und Bandbreitenlöschung von string Elementen usw. und da ging sogar das erase ohne Probleme, auch wenn die Vektorgröße dabei auf 0 gesetzt wurde.

Das "geht" immer; nur wenn du es tust, WÄHREND du über selbigen vector iterierst, zerballerst du dir damit deinen Iterator.
 
Habe den Code schon vor längerer Zeit etwas überarbeitet und es funktioniert alles soweit auch ganz gut, nur wird jetzt immer das unterste Sprite gelöscht. Möchte aber das oberste löschen, wenn mehrere Sprites übereinander liegen, also alles anders herum. Hab schon viele versch. Dinge probiert, aber es klappt nicht. Bräuchte einen kurzen Tipp oder Hinweis, wo man ansetzen könnte. Vielen Dank im Voraus.

Code:
#include <SFML/Graphics.hpp>
#include <vector>

int main()
{
    sf::RenderWindow mMainWindow(sf::VideoMode(1200, 900), "Window");
    mMainWindow.setFramerateLimit(60);

    sf::Texture texture;
    texture.loadFromFile("settlement.png");

    std::vector<sf::Sprite> SpriteVector;
	std::vector<sf::Sprite>::iterator SpriteIt;
	
    while (mMainWindow.isOpen())
    {
        sf::Event event;

		bool creating = false;
		bool rightclick = false;

		sf::Vector2i mousePos;

        while (mMainWindow.pollEvent(event))
        {
			switch (event.type)
			{
			case sf::Event::Closed:
				mMainWindow.close();
				break;
			case sf::Event::KeyPressed:
				creating = (event.key.code == sf::Keyboard::A);
				break;
			case sf::Event::MouseButtonPressed:
				if (event.mouseButton.button == sf::Mouse::Right)
				{
					rightclick = true;
					mousePos = sf::Vector2i (event.mouseButton.x, event.mouseButton.y);
					break;
				}
			}
		}
		if (creating)
		{
			sf::Sprite newSprite;
			newSprite.setTexture(texture);
			newSprite.setPosition(mMainWindow.mapPixelToCoords(sf::Vector2i(sf::Mouse::getPosition(mMainWindow).x-50,sf::Mouse::getPosition(mMainWindow).y-50))); 
			SpriteVector.push_back(newSprite);
		}
		if (rightclick)
		{
			for (auto& SpriteIt = SpriteVector.begin(); SpriteIt != SpriteVector.end(); ++SpriteIt)
            {
                sf::Vector2f mousecoords(mMainWindow.mapPixelToCoords(sf::Vector2i(event.mouseButton.x, event.mouseButton.y)));
                if (SpriteIt->getGlobalBounds().contains(mousecoords))
				{
                    SpriteVector.erase(SpriteIt);
					break;
				}
            }
        }	
	
    mMainWindow.clear();
    for(auto &SpriteIt = SpriteVector.begin();SpriteIt != SpriteVector.end();++SpriteIt)
	{
		mMainWindow.draw(*SpriteIt);
	}
    mMainWindow.display();
    }
    return 0;
}
 
Du könntest den vector entweder vorm Iterieren umdrehen (std::reverse), oder rückwärts iterieren ... einfach std::vector::reverse_iterator statt std::vector::iterator nutzen (bzw. std::vector::rbegin und std::vector::rend).
 
In Zeile 13 habe ich jetzt (möchte den reverse iterator verwenden):

Code:
std::vector<sf::Sprite>::reverse_iterator SpriteIt;

Aber wie erwartet gibt es keine Änderung.

Eine zusätzliche Änderung in Zeile 52 auf:
Code:
for(SpriteIt = SpriteVector.begin();SpriteIt != SpriteVector.end();SpriteIt++)
fürht zu noch mehr Fehlern.....



Wenn ich statt dem Ganzen einfach zwischen Zeile 51/52 folgenden Code einfüge:
Code:
std::reverse(std::begin(SpriteVector), std::end(SpriteVector));

Löscht er tatsächlich das oberste Sprite, allerdings macht er bei allen Sprites auf der "Karte" einen "Globaltausch" bei jedem Klick und das
sieht unschön aus...gibts da keine andere Möglichkeit....
 
Zuletzt bearbeitet:
Bronislaw schrieb:
In Zeile 13 habe ich jetzt (möchte den reverse iterator verwenden):

Code:
std::vector<sf::Sprite>::reverse_iterator SpriteIt;

Aber wie erwartet gibt es keine Änderung.

Eine zusätzliche Änderung in Zeile 52 auf:
Code:
for(SpriteIt = SpriteVector.begin();SpriteIt != SpriteVector.end();SpriteIt++)
fürht zu noch mehr Fehlern.....

Bitte Beiträge sorgfältig durchlesen. :) Wie ich schon sagte, wenn du einen reverse_iterator nutzt, dann mußt du auch rbegin() statt begin() und rend() statt end() aufrufen, um die korrekten Start/End-Iteratoren zu bekommen.
Ergänzung ()

Im Übrigen ist die Deklaration/Definition

Code:
std::vector<sf::Sprite>::iterator SpriteIt;

in Zeile 13 ohnehin vollkommen überflüssig, da du diese Variable so wie so nie verwendest. In den Zeilen 52 und 64 legst du schließlich mit auto eine gleichnamige, aber dennoch neue und auf den scope der jeweiligen Schleife beschränkte Variable ein, die innerhalb des Schleifenblocks die in Zeile 13 deklarierte Variable verdeckt.
 
So, habe jetzt scheinbar doch noch eine Lösung gefunden. Das Geheimnis war std::reverse an 2 versch. Stellen einzusetzen.
Wenn man nicht gerade irgendwo ins Leere klickt, dann tauscht er auch nicht ständig rum. :-)
Rend und Rbegin+Reverse Iterator gingen leider nicht , weil es sich irgendwie nicht mit erase verträgt. Er scheint beim erase den Reverse Iterator nicht zu mögen....

Code:
#include <SFML/Graphics.hpp>
#include <vector>

int main()
{
    sf::RenderWindow mMainWindow(sf::VideoMode(1200, 900), "Window");
    mMainWindow.setFramerateLimit(60);

    sf::Texture texture;
    texture.loadFromFile("settlement.png");

    std::vector<sf::Sprite> SpriteVector;
	
    while (mMainWindow.isOpen())
    {
        sf::Event event;

		bool creating = false;
		bool rightclick = false;

		sf::Vector2i mousePos;

        while (mMainWindow.pollEvent(event))
        {
			switch (event.type)
			{
			case sf::Event::Closed:
				mMainWindow.close();
				break;
			case sf::Event::KeyPressed:
				creating = (event.key.code == sf::Keyboard::A);
				break;
			case sf::Event::MouseButtonPressed:
				if (event.mouseButton.button == sf::Mouse::Right)
				{
					rightclick = true;
					mousePos = sf::Vector2i (event.mouseButton.x, event.mouseButton.y);
					break;
				}
			}
		}
		if (creating)
		{
			sf::Sprite newSprite;
			newSprite.setTexture(texture);
			newSprite.setPosition(mMainWindow.mapPixelToCoords(sf::Vector2i(sf::Mouse::getPosition(mMainWindow).x-50,sf::Mouse::getPosition(mMainWindow).y-50))); 
			SpriteVector.push_back(newSprite);
		}
		if (rightclick)
		{
			std::reverse(std::begin(SpriteVector), std::end(SpriteVector));
			for (auto& SpriteIt = SpriteVector.begin(); SpriteIt != SpriteVector.end(); ++SpriteIt)
            {
                sf::Vector2f mousecoords(mMainWindow.mapPixelToCoords(sf::Vector2i(event.mouseButton.x, event.mouseButton.y)));
                if (SpriteIt->getGlobalBounds().contains(mousecoords))
				{
					
					SpriteVector.erase(SpriteIt);
					std::reverse(std::begin(SpriteVector), std::end(SpriteVector));
					break;
				}

            }
        }	
	
    mMainWindow.clear();
    for(auto &SpriteIt = SpriteVector.begin();SpriteIt != SpriteVector.end();++SpriteIt)
	{
		mMainWindow.draw(*SpriteIt);
	}
    mMainWindow.display();
    }
    return 0;
}
 
Bronislaw schrieb:
Rend und Rbegin+Reverse Iterator gingen leider nicht , weil es sich irgendwie nicht mit erase verträgt. Er scheint beim erase den Reverse Iterator nicht zu mögen....

Sorry, diese Falltür hatte ich vergessen. Gib nicht immer gleich auf! Eine kurze Google-Suche nach reverse_iterator erase hätte dich in Null Komma nix zu diesem Ergebnis geführt:

How to call erase with a reverse iterator

m_CursorStack.erase( std::next(i).base() );

wobei i ein reverse_iterator ist.
 
Zurück
Oben