Problem mit List, Iterator und Pointern auf Objekte



  • Guten Abend. Ich programmiere momentan an einem C++ Spiel das viele Bildobjekte verarbeitet.
    Leider bekomme ich mit VS2017 immer wieder folgende Fehlermeldung und ich weiß nicht wieso.

    Ausnahmefehler bei 0x5959E906 (ucrtbased.dll) in SDL2GameCPP.exe: Ein ungültiger Parameter wurde an eine Funktion übergeben, die ungültige Parameter als schwerwiegend einstuft.

    Es scheint dabei um die erase Funktion zu gehen in der ich das Objekt in der Liste per Iterator löschen möchte.
    Hier ein Codeausschnitt:

    // Kollision zwischen Schussliste und Fensterrahmen:
    for(m_ItShot = m_ShotList.begin(); m_ItShot != m_ShotList.end(); ++m_ItShot) {
    	if(m_ItShot->OL().x <= 0 || m_ItShot->OL().x >= SCREEN_WIDTH 
    	|| m_ItShot->OL().y <= -100 || m_ItShot->OL().y >= SCREEN_HEIGHT ) {
    		m_ItShot = m_ShotList.erase(m_ItShot);
    	}	
    }
    

    Vorher erzeuge ich die Objekte mit folgendem Code:

    if(m_bFire && m_pShotTimer->Timeout()) {
    		
    CSprite *pShot = new CSprite(180,8.0f);
    pShot->Load("img/schiff/shotimage2.png");
    pShot->SetPos(m_pRaumschiffLinksRechts->GetPos().x + 160,m_pRaumschiffLinksRechts->GetPos().y + 120);
     m_ShotList.push_back(*pShot);
    		
    }
    

    Hier noch ein Bild vom Debugger:
    Bilddatei
    MfG _andi84



  • Wenn du ein erase machst und das letzte Element löscht, zeigt dein Iterator danach auf end. Dann nach dem ++mIt_Shot ist es einen hinter end und wird auch niemals mehr gleich dem end sein.

    Schau dir das Beispiel hier an: https://en.cppreference.com/w/cpp/container/vector/erase
    Du musst das ++ im Nicht-Erase-Fall machen und nicht im Schleifenheader.

    Edit: schau dir auch das erase-remove-Pattern an.



  • @wob sagte in Problem mit List, Iterator und Pointern auf Objekte:

    Nicht-Erase-Fall

    Also in etwa so ?

    m_ItShot = m_ShotList.begin();
    
    	while (m_ItShot != m_ShotList.end()) {
    		if (m_ItShot->OL().x <= 0 || m_ItShot->OL().x >= SCREEN_WIDTH
    			|| m_ItShot->OL().y <= -100 || m_ItShot->OL().y >= SCREEN_HEIGHT) {
    			m_ItShot = m_ShotList.erase(m_ItShot);
    			++m_ItShot;
    		}
    	}
    


  • @_andi84 sagte in Problem mit List, Iterator und Pointern auf Objekte:

    Also in etwa so ?

    Nein. Ist es zu viel verlangt, wenn du dir das oben verlinkte Beispiel der Doku anguckst?



  •         // Kollision zwischen Schussliste und Fensterrahmen:
    	for(auto m_ItShot = m_ShotList.begin(); m_ItShot != m_ShotList.end();) {
    		if( m_ItShot->OL().x <= 0 || m_ItShot->OL().x >= SCREEN_WIDTH 
    		|| m_ItShot->OL().y <= -100 || m_ItShot->OL().y >= SCREEN_HEIGHT ) {
    			m_ItShot = m_ShotList.erase(m_ItShot);
    		}
    		else {
    		        ++m_ItShot;
    		}
    	}
    

    Ich glaube ich habe Schwierigkeiten zu verstehen ob der Iterator eine Position oder ein Pointer auf ein Objekt ist.
    Bei meinem Beispiel hier wird dann also der Iterator im Löschfall auf das Ende gesetzt ? Also wäre m_ItShot = m_ShotList.erase(m_ItShot); genau so wie m_ItShot = m_ShotList.end(); ?



  • OT:

    @_andi84 sagte in Problem mit List, Iterator und Pointern auf Objekte:

    m_ShotList.push_back(*pShot);

    Dir ist schon klar, dass du hier eine Kopie in die Liste steckst und den Speicher nicht freigibst? Warum new?



  • Mit m_ShotList.clear() kann ich doch alles wieder freigeben ?
    Das ist ja auch ein unvollständiges Codebeispiel.



  • Da:

    @_andi84 sagte in Problem mit List, Iterator und Pointern auf Objekte:

    m_ShotList.push_back(*pShot);
    

    wird das Objekt auf das pShot zeigt kopiert. Das "Original", das worauf pShot zeigt wird nie freigegeben. Warum new?



  • zu müde. heute wird das nichts mehr. 😴 🙂



  • @_andi84 sagte in Problem mit List, Iterator und Pointern auf Objekte:

    Ich glaube ich habe Schwierigkeiten zu verstehen ob der Iterator eine Position oder ein Pointer auf ein Objekt ist.

    Man kann einen Iterator als Verallgemeinerung solcher Konzepte wie "Pointer" oder "Position" betrachten. "Position" passt eigentlich ganz gut, allerdings nicht im konkreten Sinne z.B. eines Array-Index, sondern eher als abstrakte Position.

    Bei meinem Beispiel hier wird dann also der Iterator im Löschfall auf das Ende gesetzt ? Also wäre m_ItShot = m_ShotList.erase(m_ItShot); genau so wie m_ItShot = m_ShotList.end(); ?

    Nein. erase() gibt einen Iterator auf das nachfolgende Element zurück. Nur wenn es kein solches gibt, weil du das letzte Element gelöscht hast, wird der end()-Iterator zurückgegeben.

    Ansonsten: Ausschlafen und nochmal in Ruhe ansehen. Vor allem die Anmerkungen zu dem unnötigen und fehlerhaft verwendeten new.



  • 
    // Dieses habe ich als Membervariablen der Klasse CGame festgelegt:
    	list<CSprite> m_ShotList;
    	list<CSprite>::iterator m_ItShot;
    
    // Dies rufe ich in der Spawn Funktion auf:
    	if(m_bFire && m_pShotTimer->Timeout()) {
    	CSprite pShot(180,8.0f);
    	pShot.Load("img/schiff/shotimage2.png");
    	pShot.SetPos(m_pRaumschiffLinksRechts->GetPos().x + 160,m_pRaumschiffLinksRechts->GetPos().y + 120);
     	m_ShotList.push_back(pShot);		
    	}
    

    So sieht das jetzt mit dem erase aus:

    while(m_ItShot != m_ShotList.end()) {
    
    for(m_ItBallon = m_BallonList.begin(); m_ItBallon != m_BallonList.end(); ++m_ItBallon) {
    						
    	if(Entfernung(m_ItShot->GetPos().x,m_ItShot->GetPos().y,m_ItBallon->GetPos().x,m_ItBallon->GetPos().y) <= 30.0f) {
    		m_ItShot = m_ShotList.erase(m_ItShot);
    		m_nPunktestand += 30;
    	} else {
    		++m_ItShot;
    	}
    }}
    

    Leider wird nichts mehr gerendert.

    // So iteriere ich durch die Liste:
    	m_ItShot = m_ShotList.begin();
    	while( m_ItShot != m_ShotList.end()) {
    		m_ItShot->Render(0);
    		++m_ItShot;
    	}
    

    Ich weiß nicht mehr weiter 😭



  • Was soll denn jetzt die for Schleife in der while Schleife?



  • Also ich hätte noch einen weiteren allgemeinen Tipp zu deinem ersten Beispiel (ist das jetzt gelöst? Der neue Code sieht ja doch ganz anders aus!). Und zwar würde ich da noch empfehlen, dieses Riesen-if hier (if( m_ItShot->OL().x <= 0 || m_ItShot->OL().x >= SCREEN_WIDTH || m_ItShot->OL().y <= -100 || m_ItShot->OL().y >= SCREEN_HEIGHT ) {) in eine Funktion zu schreiben. Zum Beispiel bool hasLeftScreen(const Sprite &) (oder auch andersrum: isOnScreen, je dachdem, was besser passt). Dann kann man sowas wie myList.erase(remove_if(begin(myList), end(myList), hasLeftScreen), end(myList)) schreiben und sofort ist der Befehl klar (und vor allem richtig, ohne dass du über einen Index oder sowas nachdenken musst).

    Was willst du jetzt in deinem neuen Quelltext tun - jeden Shot mit jedem Ballon vergleichen? Jedes mit jedem klingt immer irgendwie gefährlich langsam, vor allem wenn du dann noch Listen hast (hast du eigentlich einen guten Grund für std::list? Aber andere Baustelle...). Wenn beides wenige Elemente enthält, dann vergleiche meietwegen auch alles mit allem. Aber dann wäre es vielleicht hilfreich, die innere Schleife in eine Funktion zu tun und zu benennen.



  • Was willst du jetzt in deinem neuen Quelltext tun - jeden Shot mit jedem Ballon vergleichen? Jedes mit jedem klingt immer irgendwie gefährlich langsam

    Ja das stimmt. Ich muss alles nochmal neu überdenken. Danke für die Hilfe. Ich melde mich mal falls ich weiter gekommen bin.



  • Ich habe jetzt wirklich viel herumprobiert und komme zu dem Ergebnis das die folgende Methode
    Pointer von Objekten in einer Liste zu hinterlegen als einzige in meinem Projekt zu gerenderten Ergebnissen
    führt. Ich muss wohl eher einen Fehler gemacht haben als ich die vielen verschachtelten Schleifen erstellt habe.
    Wenn ich die Kollisionsschleifen in meiner Gameschleife auskommentiere bekomme ich auch keine Fehlermeldung.

    list<CObjekt> ObjektListe;
    list<CObjekt>::iterator ObjektIterator;
    
    while( TimerA >= TimerB) {
       CObjekt *pPointerX = new CObjekt( BspWertA, BspWertB );
       pPointerX->Memberfunktion("BspDateiname.png");
       ObjektListe.push_back(*pPointerX);
       TimerA = 0;
    }
    
    for(ObjektIterator = ObjektListe.begin(); ObjektIterator != ObjektListe.end(); ++ObjektIterator) {
       ObjektIterator->Update();
       ObjektIterator->Render();
    }
    
    ObjektListe.clear();
    


  • Warum "new"? Du hast da einen schönen Memory Leak produziert.



  • Wie teste ich mein Programm effektiv auf Memory Leaks ? Etwa mit VS 2019 Community ?

    Link Text



  • @_andi84 sagte in Problem mit List, Iterator und Pointern auf Objekte:

    Pointer von Objekten in einer Liste zu hinterlegen

    Du hinterlegst keine Pointer!



  • Habe folgende Anleitung gefunden:
    Memory Leaks finden

    Und Ja es gibt welche:
    Screenshot



  • @_andi84 Für Linux gibt es Valgrind, was da recht mächtig ist. Für Windows gibt es auch tools, die nach Speicher Problemen suchen können, siehe vlt: https://stackoverflow.com/questions/413477/is-there-a-good-valgrind-substitute-for-windows

    Regel Nummer 1 um Memory Leaks zu verhindern: Keine manuelle Speicherverwaltung (kein new)!


Log in to reply