Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

eXpl0it3r

Treue Seele

Beiträge: 386

Wohnort: Schweiz

Beruf: Professional Software Engineer

  • Private Nachricht senden

31

23.06.2014, 13:47

Aus meiner Sicht, sollte für einfache Ressourcen ein shared_ptr völlig ausreichen. Der Ressource Manager kann dann ein shared_ptr "cachen" und eine bestimmte Release Strategie anwenden. Wenn man explizit eine Ressource löschen will, dann gibt es da wohl mehrere Optionen. Zum einen kann den gecached shared_ptr einfach entfernen und hoffen, dass ihn niemand mehr braucht, oder aber man könnte ihn Zwischenspeichern falls noch andere Referenzen existieren und in einem zufälligen Intervall auf Löschung prüfen, oder aber man könnte eine assert auslösen, falls andere Referenzen noch existieren und den gecachten shared_ptr freigeben. Persönlich denke ich, dass ein assert eine Gute Lösung ist, da das vorzeitige Löschen einer Ressource auf ein Logikfehler hindeutet, es aber im Fall, dass ein Bug sich doch noch durch die Debug Version geschlagen, in der Release Version funktioniert, halt aber evtl. zu viel Speicher alloziert.

Im Endeffekt kommt es dann natürlich auch noch stark darauf an, wer denn nun die Ressourcen managet (z.B. in einem State, oder State übergreifend, etc.).

Falls es noch weiter Codebeispiele braucht, kann man sich mal noch Thor's Ressourcen Management anschauen, da gibt es auch verschiedene Strategieeinstellungen.

Ich wollte eigentlich wissen, wie der Destruktor des shared_ptr sicherstellt, dass falls er die Ressource (bspw. dynamisch alloziierter Speicher) löschen soll, diese in JEDEM Fall freigegeben wird. Was macht er also, wenn ein Fehler (Exception) auftritt? Propbiert er es endlos erneut, oder kehrt er einfach zum Aufrufer zurück, und lässt die Ressource in einem ungültigen Zustand zurück? Wie stellt er seine Exception-Garantie sicher?
Evtl. hab ich dich jetzt falsch verstanden, aber das ist ja grad das geniale am shared_ptr. Wenn ein shared_ptr out-of-scope geht, dann wird die referenzierte Ressource nur dann freigegeben, falls es der letzte shared_ptr ist. In allen anderen Fällen, wird einfach den geteilten Reference Counter hinunter gezählt. Wenn also nun dein Programm "normal" beendet wird, dann werden alle shared_ptr gelöscht, d.h. alles Destruktoren der shared_ptr werden aufgerufen und somit wird der Reference Counter hinunter gezählt bis es nur noch einen gibt und der Destruktor dessen gibt dann die Ressource frei.
Blog: https://dev.my-gate.net/
—————————————————————————
SFML: https://www.sfml-dev.org/
Thor: http://www.bromeon.ch/libraries/thor/
SFGUI: https://github.com/TankOs/SFGUI/

32

23.06.2014, 14:34

@BlueCobold: Klar, es ist nötig Redundanz zu vermeiden. Daher würde ich eben vorschlagen, Handles zurückzugeben. Wann eine Ressource gelöscht wird, kann der Ressourcen-Manager dann nämlich selbst bestimmen (Beispiel von David Scherfgen: Wenig verfügbarer Speicher / andere Ressourcen werden dringender benötigt). Also lasse ich mein System erst einmal so wie es ist, da ich momentan immerhin garantieren kann, dass (unter Verwendung des Ressourcen-Managers) keine Ressource zweimal zur selben Zeit im Speicher existiert.

@expl0it3r: Ja, da hast du mich falsch verstanden ;) ( ich habe mich anscheinend aber auch stark missverständlich ausgedrückt). Was ich wissen wollte war, ob, und wenn ja wie der std::shared_ptr sicherstellen kann, dass die Ressource endgültig gelöscht wird (Beispiel: Custom Deallocator wirft eine Exception, da Datenbank-Verbindung nicht geschlossen werden konnte). Laut Evrey wird das Programm in diesem Fall wohl über std::terminate() beendet. Das Prinzip des Reference Countings an sich habe ich verstanden, ich habe es sogar selbst implementiert. Allerdings innerhalb der Klassen, sodass nach außen hin weder std::shared_ptr noch sonst ein Wrapper benötigt wird. Daher war meine Frage ja auch, ob dieses interne Reference Counting sinnvoll ist.

@Legend: In der Tat, std::shared_ptr ohne std::weak_ptr macht wohl wenig Sinn. Bei den Handles halte ich es momentan so, dass die Handles zu einfachen Pointer dereferenziert werden, wenn sie benötigt werden. Diese Pointer werden allerdings nicht dauerhaft gespeichert. Stattdessen wird eine Kopie des Handles gespeichert, damit sichergestellt werden kann, dass ein gültiger Zeiger (oder nullptr, wenn die Ressource nicht geladen werden konnte) verwendet wird (bei nullptr wird dieser natürlich nicht verwendet ;) ), auch wenn der Ressourcen-Manager Dinge wie beispielsweise Memory Defragmentation (noch nicht implementiert) betreibt oder die Ressource in der Zwischenzeit gelöscht und neu geladen wurde (dementsprechend in einen anderen Ort im Speicher).

Evrey

Treue Seele

Beiträge: 245

Beruf: Weltherrscher

  • Private Nachricht senden

33

23.06.2014, 15:48

Erstens: std::shared_ptr löscht das Objekt, sobald der Referenz-Zähler auf 0 fällt. Ganz normal, ganz offensichtlich, bloß mit dem Unterschied, dass der Zähler nicht im Objekt enthalten ist. Einen Crash gibt es nur dann, wenn der Destruktor deines Objekts versagt, und dann wäre es salopp gesagt eigene Dummheit, die das Programm tötet. Zweitens: Wenn man ganz sicher sein will, dass der Manager auf jeden Fall der letzte ist, der löscht, dann könnte man auch std::weak_ptr verteilen, die Exceptions auslösen, falls man über sie auf tote Objekte zugreifen will.

Zitat

(Beispiel: Custom Deallocator wirft eine Exception, da Datenbank-Verbindung nicht geschlossen werden konnte)
Warum sollte der Deallocator sowas tun? Es gibt für meine Allokatoren nur einen Fehler, der auftreten kann, wenn man deallokiert: Eine Assertion, wenn der Speicher nicht von genau diesem Allokator geholt wurde. Und da auch wirklich assert, denn das wäre nicht zu tolerieren.

C-/C++-Quelltext

1
2
3
4
int main(int _argc, char** _argv) noexcept {
  asm volatile("lock cmpxchg8b %eax");
  return 0;
} // ::main
(Dieses kleine Biest vermochte einst x86-Prozessoren lahm zu legen.)

=> Und er blogt unter Hackish.Codes D:

34

23.06.2014, 15:54

Stell dir einfach eine Funktion einer externen API db.Close() vor, die eben im Destruktor aufgerufen wird. Diese kann allerdings aus was für Gründen auch immer eine Exception werfen. In solchen Fällen "schlucke" ich die Exception wie gesagt einfach. Oder gibt es da eine bessere Variante?

Evrey

Treue Seele

Beiträge: 245

Beruf: Weltherrscher

  • Private Nachricht senden

35

23.06.2014, 16:12

Ich kann mir nur einen Fall vorstellen, in dem es sinnvoll wäre, dass das Schließen einer Verbindung fehlschlagen könnte. Und zwar im Fall einer nicht-RAII-Verbindung, bei der theoretisch gesehen ein doppeltes Schließen möglich wäre. Dann aber wäre es Unbedachtheit, die die Exception auslöst, weshalb ich da std::terminate willkommen heißen würde. Ansonsten gilt halt: Exception im Destruktor abfangen, retten was zu retten ist, und dann entscheiden, ob es noch sinnvoll ist, das Programm weiterlaufen zu lassen.

C-/C++-Quelltext

1
2
3
4
int main(int _argc, char** _argv) noexcept {
  asm volatile("lock cmpxchg8b %eax");
  return 0;
} // ::main
(Dieses kleine Biest vermochte einst x86-Prozessoren lahm zu legen.)

=> Und er blogt unter Hackish.Codes D:

Sacaldur

Community-Fossil

Beiträge: 2 301

Wohnort: Berlin

Beruf: FIAE

  • Private Nachricht senden

36

23.06.2014, 16:21

Stell dir einfach eine Funktion einer externen API db.Close() vor, die eben im Destruktor aufgerufen wird. Diese kann allerdings aus was für Gründen auch immer eine Exception werfen. In solchen Fällen "schlucke" ich die Exception wie gesagt einfach. Oder gibt es da eine bessere Variante?

Es ist schwer, anhand konstruierter Fälle über mögliche Lösungswege zu schreiben. In deinem Fall hast du Texturen oder ähnliche Daten, die aus dem Dateisystem in den Arbeitsspeicher geladen werden und beim Abräumen muss nur noch der Speicher freigegeben werden. (Warum sollten solche Ressourcen irgendwas mit (Netzwerk-)Verbindungen anfangen?)
Ich muss zugeben, dass ich bisher nicht mit C++ gearbeitet habe, nur ist es für mich schwer vorstellbar, dass in diesem Fall irgendeine Exception anfallen könnte (die man mit einer anderen Lösung besser abfangen könnte).
Du solltest dir über Exceptions also erst dann Gedanken machen, wenn auch die Gefahr des Auftretens dieser besteht.
Spieleentwickler in Berlin? (Thema in diesem Forum)
---
Es ist ja keine Schande etwas falsch zu machen, als Programmierer tu ich das täglich, [...].

Werbeanzeige