Kurze Fragen zu Klassen und Vector resp. List

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Kurze Fragen zu Klassen und Vector resp. List

Beitrag von starcow »

Guten Abend Zusammen

Ich bin dabei ein kleines Motörchen für ein mini Spiel zu bauen und stehe bei einem Problem an.
Das Mini-Spiel sollte später einmal in der in der Lage sein, Spieler und Kreaturen als Objekte zu erzeugen, diese mittels einfachem würfeln gegeneinander antreten zu lassen und diese gegeben Falls wieder ins Nirvana zu befördern :-)

Ueberlegt habe ich mir dabei folgenes:
Die Engine (also das Motörchen, ;-)), verwaltet dabei alle Listen der Objekte. Also die Player-Liste und die Kreaturen-Liste.
Soll ein Objekt erzeugt oder gelöscht werden, geht das nur über die Methode, welche die Engine zu Verfügung stellt.
CreatePlayer(), DeletePlayer(), CreateCreature(), DeleteCreature().

Eine weitere Ueberlegung war, dass wenn das Engine-Objekt selbst gelöscht wird, sämtliche in den Listen enthaltenen Objekte ebenfalls gelöscht werden.
Quasi ein sauberes "herunterfahren".
Dazu rufe ich dann einfach die entsprechende Methode (DeletePlayer()) im Desturktur des Engine-Objekts auf.

Die main funktion sieht folglich noch sehr bescheiden aus:

Code: Alles auswählen

	BEAST_Engine engine;
	engine.CreatePlayer();
	engine.DeletePlayer();

	return 0;
Die Methoden (Ich beschränke mich hier im Beispiel auf die Player Klasse)

Code: Alles auswählen

int BEAST_Engine::CreatePlayer() {
	cout << "- Methode CreatePlayer von BEAST_Engine wurde aufgerufen" << endl;
	LISTplayer.push_back(new BEAST_Player);
	return 0;
}

int BEAST_Engine::DeletePlayer() {
	cout << "- Methode DeletePlayer von BEAST_Engine wurde aufgerufen" << endl;
	for (iLISTplayer = LISTplayer.begin(); iLISTplayer != LISTplayer.end(); ++iLISTplayer) {
		delete (*iLISTplayer);
	}
	return 0;
}
Das ganze lässt sich auch ohne Fehler oder Warnings compilieren. Starte ich das Programm, kriege ich aber ein Fehler in einer Dialogbox.
"Debug Assertion Failed!
Program: (Dateipfad)
File: ...debug_heap.cpp
Line:888

Expression: _CrtIsValidHeapPointer(block)
"
Mit dem Vermerk, ich soll doch mal die Jungs und Mädels von zfx fragen :-D
Irgendwie scheine ich da etwas zu machen, was nicht zulässig ist.
Ich kann mir aber keinen Reim drauf machen, denn wenn ich das engine.DeletePlayer(); in der main entferne, läuft das Programm ohne Fehlermeldung ab.

Eine andere Frage, die in dem Zusammenhang aufgetaucht ist, ist folgende:
Darf ich mich bei einem vector oder einer list (STL) darauf verlassen, das die Adresse eines Objektes immer konstant bleibt.
Das wäre nämlich Voraussetzung für mein Konzept.
Zufälligerweise bin ich bei golem.de in einem Beitrag über C und Rust auf folgende Aussage eines Users gestossen:
"Damit deckst du leider nicht alles ab. Wenn du bspw. eine Referenz zu einem Vector-Element hast und du fügst ein weiteres Element in Vector hinzu und der Vector macht intern ein realloc, weil der Speicherplatz nicht mehr ausreicht, dann wird deine alte Referenz ungültig. "
https://forum.golem.de/kommentare/secur ... ,read.html

Wenn das richtig ist, dann dürfte man ja eigentlich gar keine Pointers anlegen, welche auf Objekte zeigen, die sich in einem Vector befinden.

Gruss starcow
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Kurze Fragen zu Klassen und Vector resp. List

Beitrag von Krishty »

starcow hat geschrieben:Wenn das richtig ist, dann dürfte man ja eigentlich gar keine Pointers anlegen, welche auf Objekte zeigen, die sich in einem Vector befinden.
… so ist es. Irgendwann, wenn der Speicherplatz nicht mehr ausreicht (abhängig von der vector-Implementierung wird da viel optimiert und reserviert, deshalb muss es nicht beim 2. push_back() sein), werden deine ganzen Objekte in einen anderen Speicherblock kopiert und deine Zeiger werden ungültig.

Wenn du trotzdem durch die alten Zeiger zugreifst, wird der Speicher beschädigt, und die Heap-Assertions beginnen. (Die tauchen nicht zwingend beim problematischen Zugriff auf, sondern erst bei einer der nächsten Heap-Operationen – z.B. bei new, push_back() oder im Destruktor ganz anderer Listen – darum sind sie scheinbar zufällig und ihre Ursachen schwer aufzuspüren.)

Gute Alternativen muss dir einer der C++-Profis hier empfehlen; mir fällt als erstes vector<unique_ptr<T>> ein.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
xq
Establishment
Beiträge: 1581
Registriert: 07.10.2012, 14:56
Alter Benutzername: MasterQ32
Echter Name: Felix Queißner
Wohnort: Stuttgart & Region
Kontaktdaten:

Re: Kurze Fragen zu Klassen und Vector resp. List

Beitrag von xq »

Würde auch für vector<unique_ptr<T>> verwenden oder eben eine Baumstruktur... Je nach dem, wie dein Projekt strukturiert ist. Vielleicht ist es auch sinnvoll, nur eine Liste für Spieler und Kreaturen zu nehmen und dann halt über Vererbung unterscheiden. Das dürfte das Projekt leichter erweiterbar machen
War mal MasterQ32, findet den Namen aber mittlerweile ziemlich albern…

Programmiert viel in ⚡️Zig⚡️ und nervt Leute damit.
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: Kurze Fragen zu Klassen und Vector resp. List

Beitrag von starcow »

Danke euch für die Hilfestellung.

Ich habe realisiert, dass ich einen fundamentalen Ueberlegungsfehler gemacht habe! Und dies war auch der Grund, weshalb bei Laufzeit des Programms ein Fehler aufgetreten war:

Code: Alles auswählen

for (iLISTplayer = LISTplayer.begin(); iLISTplayer != LISTplayer.end(); ++iLISTplayer) {
delete (*iLISTplayer);
}
Was hier gelöscht wird, ist ja kein Element aus der Liste, sondern das "freie" Objekt auf dem Heap.
Die Liste mit den Zeigern auf die Objekte bleibt ja unangetastet - und bei einem erneuten Aufruf der Funktion wird versucht, nochmals sämtliche Objekte zu löschen, welche zuvor bereits gelöscht wurden.
Natürlich muss man nach dem Löschen des Objektes den Zeiger *iLISTplayer auf Null setzen.

Code: Alles auswählen

*iLISTplayer = NULL;
Es wäre natürlich auch angebracht, nun den Zeiger auf das ehemalige Objekt aus der Liste zu entfernen.

Dieser Umstand rückt jetzt natürlich die ganze Situation in ein anderes Licht. Weil ich somit die Adresse der Zeiger selbst nicht zu verwenden brauch. Solange garantiert ist, dass die Objekte selbst konstante Adressen besitzen (nach allem was ich weiss, muss dies in jedem Fall garantiert sein), braucht man die Adressen der Zeiger nicht zu verwenden.

Trotzdem vielen Dank für die wertvolle Info. Gehört wohl ziemlich klar in die Kategorie "Dinge die man dringen wissen muss". :)

Gruss starcow
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Antworten