Flexibler Zugriff auf Ressourcen
-
Nexus schrieb:
Werner Salomon schrieb:
kann ResourceManager<Image> imageMgr; hier nicht ein Singleton sein, denn was sollte die Existenz von mehr als einem ResourceManager nur für Images rechtfertigen? Und wenn das ein Singleton ist, dann kann das doch in einer Funktion oder statischen Methode imageMgr_Acquire(...) angesprochen werden. Der werte Anwender muss das also gar nicht zu Gesicht bekommen.
Doch, der Anwender muss ja wissen, wie er das Bild laden will (d.h. Argumente für
Acquire()übergeben). Möglicherweise habe ich dich hier aber auch falsch verstanden...Die Parameter, die an die Funktion image_Acquire(...) übergeben werden, sind natürlich die gleichen, die an eine Methode ResourceManager<Image>::Acquire(...) übergeben werden. Aber wozu muss der Anwender ResourceManager<Image> kennen?
Nexus schrieb:
Werner Salomon schrieb:
Wie wäre es, wenn Du statt ResourcePtr<Image> gleich einen boost::shared_ptr< Image > benutzt.
Wollte ich ursprünglich auch, aber mir schien der
shared_ptrzu wenig flexibel. Ich muss nämlich eine Aktion festlegen können, sobald der Ref-Count auf 1 (nicht 0) sinkt: Dies ist genau dann der Fall, wenn eine Ressource nur noch imResourceManagerselbst und nirgends ausserhalb mehr referenziert wird. Je nachdem soll diese Situation nämlich zur Freigabe der Ressource führen.kannst Du mit shared_ptr machen, wenn im ResourceManager nur ein weak_ptr auf dieses Objekt verbleibt. Schau Dir mal dazu die Doku weak_ptr an.
Nexus schrieb:
Werner Salomon schrieb:
Eine Funktion, die so einen Pointer zurück liefert, kann beim Erstellen des shared_ptr auch ein geeignetes Release-Objekt mitgeben.
Das klingt interessant, eventuell könnte ich dem
ResourcePtreine Art Deleter mitgeben lassen. Ist bei euch der Fall schon einmal vorgekommen, in dem ihr einen Null-Deleter (der nichts tut) eingesetzt habt?ja hatte ich gerade neulich, als ich einen Pointer auf ein xerces::DOMDocument in einen shared_ptr unterbringen wollte, aber auch den Fall abdecken musste, dass dieser Pointer aus einem xerces::Parser kommt, der selbst das Document wieder aufräumt.
Nexus schrieb:
Ich befürchte einfach, dass ein einzelner Smart-Pointer mit unterschiedlichen Besitzsemantiken zu Verwirrung führen könnte.
Warum? Außerhalb ist dies immer nur ein shared_ptr mit den bekannten Verhalten eines Smart-Pointers.
Gruß
Werner
-
Werner Salomon schrieb:
Die Parameter, die an die Funktion image_Acquire(...) übergeben werden, sind natürlich die gleichen, die an eine Methode ResourceManager<Image>::Acquire(...) übergeben werden. Aber wozu muss der Anwender ResourceManager<Image> kennen?
Weil er wahrscheinlich auch selbst Kontrolle über die Ressourcenverwaltung haben will. Ausserdem ist
Imagenur ein Beispiel, als Template-Argument können beliebige benutzerdefinierte Ressourcenklassen eingesetzt werden.Werner Salomon schrieb:
kannst Du mit shared_ptr machen, wenn im ResourceManager nur ein weak_ptr auf dieses Objekt verbleibt.
Allerdings wird dann die Ressource immer freigegeben, sobald nur noch der
weak_ptrexistiert. Ich möchte aber die automatische Freigabe abhängig von einer Strategie machen. Wenn ichshared_ptrbenutze, besitzt derResourceManagerdie Ressourcen nicht mehr; seine Zerstörung führt also nicht zur Ressourcenfreigabe. MitResourcePtrist es zudem nicht möglich, eigene Zeiger zu übergeben, man kann nur auf Ressourcen innerhalb einesResourceManagers zugreifen. Aber das müsste ich ohnehin auflockern...Vielleicht wäre das sogar mit
shared_ptrirgendwie möglich, aber dann müsste ich wohl die Semantik etwas abändern und auf ein paar Garantien verzichten. Eine Konvertierung vonResourcePtrzushared_ptrwäre vielleicht auch eine Variante, hier sehe ich aber nicht, wie ich die Verwaltung unter einen Hut bringen kann.Werner Salomon schrieb:
Nexus schrieb:
Ich befürchte einfach, dass ein einzelner Smart-Pointer mit unterschiedlichen Besitzsemantiken zu Verwirrung führen könnte.
Warum? Außerhalb ist dies immer nur ein shared_ptr mit den bekannten Verhalten eines Smart-Pointers.
Wie gesagt kann plötzlich ein Dangling Pointer entstehen, wenn die Lebenszeit nicht genau kontrolliert wird. Mit
ResourcePtrist das momentan nicht möglich: Jeder Zeiger ist gültig oderNULL.
-
Nexus schrieb:
Werner Salomon schrieb:
kannst Du mit shared_ptr machen, wenn im ResourceManager nur ein weak_ptr auf dieses Objekt verbleibt.
Allerdings wird dann die Ressource immer freigegeben, sobald nur noch der
weak_ptrexistiert. Ich möchte aber die automatische Freigabe abhängig von einer Strategie machen. Wenn ichshared_ptrbenutze, besitzt derResourceManagerdie Ressourcen nicht mehr; seine Zerstörung führt also nicht zur Ressourcenfreigabe.Selbstverständlich wird der von Dir beim Acquire übergebene Deleter aufgerufen; der könnte sogar mit dem ResourceManager wieder Kontakt aufnehmen. Ein Deleter kann auch eine Zustand und alle möglichen internen Member haben.
Weiter kannst Du doch mit jedem Zugriff auf den ResourceManager entscheiden, ob Du einen weak- oder shared_ptr zurück behältst. Das bleibt doch ganz der internen Implementierung überlassen. Und eine shared_ptr kannst Du zudem fragen ob er unique ist, also keine außerhalb des Mangagers mehr vorhanden sind. Ich sehe nichts, was da fehlen sollte.Nexus schrieb:
Vielleicht wäre das sogar mit
shared_ptrirgendwie möglich, aber dann müsste ich wohl die Semantik etwas abändern und auf ein paar Garantien verzichten. Eine Konvertierung vonResourcePtrzushared_ptrwäre vielleicht auch eine Variante, hier sehe ich aber nicht, wie ich die Verwaltung unter einen Hut bringen kann.Werner Salomon schrieb:
Nexus schrieb:
Ich befürchte einfach, dass ein einzelner Smart-Pointer mit unterschiedlichen Besitzsemantiken zu Verwirrung führen könnte.
Warum? Außerhalb ist dies immer nur ein shared_ptr mit den bekannten Verhalten eines Smart-Pointers.
Wie gesagt kann plötzlich ein Dangling Pointer entstehen, wenn die Lebenszeit nicht genau kontrolliert wird. Mit
ResourcePtrist das momentan nicht möglich: Jeder Zeiger ist gültig oderNULL.Ich verstehe Deine Bedenken nicht. Warum sollte mit dem shared_ptr eine genaue Kontrolle der Lebenszeit nicht möglich sein? Kannst Du vielleicht ein Beispiel nennen.
Gruß
Werner
-
shared_ptr besitzt einen Member "deleter".
Selbes Konzept kannst du auch hier verwenden. Du erstellst also einen ResourcePtr mit einem eigenen Deleter und kannst so einen eigenen ResourceManager (oder garkeinen) verwenden.
Oder übersehe ich etwas?
-
Mit shared_ptr geht das wunderbar.
Einfachste Variante: der Resource-Manager hält selbst eine Referenz auf alle Resourcen. Dann kann er diese durchklauben, mittels shared_ptr::unique fragen welche Resourcen von aussen nichtmehr referenziert werden, und diese ggf. freigeben.
Wenn der Resource-Manager sofort mitbekommen soll, wenn die letzte Referenz von aussen verschwindet, funktioniert diese einfache Variante nicht mehr.
Allerdings kann man auch das mit einem Custom-Deleter hinbekommen.Beispiel: der Resource-Manager verwaltet die Resourcen "manuell", also ohne shared_ptr (vielleicht in einem Boost Pointer-Container oder wie auch immer).
Will eine andere Komponente nun einen shared_ptr auf die Resource haben, erstellt der Resource-Manager einen solchen, mit einem Custom-Deleter der einfach nur eine Memberfunktion des Resource-Manager aufruft ("manager->OnResourceReleased(res)").
Dann bekommt der Resource-Manager sofort einen Rückruf wenn der letzte shared_ptr verschwindet. Er kann dann entscheiden ob das Objekt sofort gelöscht werden soll, oder erst später.Natürlich müsste man dabei ein paar Details beachten:
* Der Custom-Deleter muss einen shared_ptr auf den Resource-Manager haben, sonst könnte der Resource-Manager gelöscht werden, bevor die letzten Callbacks ausgeführt werden. (Es muss kein shared_ptr sein, aber irgendwie muss man es tracken - intrusive_ptr ginge auch, manuelles Ref-Counting genauso. Und wenn das Resource-Manager statisch instanzierber sein kann, kann man es pimpln, und der Custom-Deleter referenziert direkt die "impl")
* Entweder muss sich der Resource-Manager einen weak_ptr auf Objekte behalten, die er schonmal hergegeben hat, damit er bei weiteren Anfragen nach dem selben Objekt einen shared_ptr erzeugen kann der sich den selben "Counter" mit bereits existierenden shared_ptr teilt. Oder er muss für jedes Objekt mitzählen wieviel shared_ptr er hergegeben hat (Inkrement beim "get" und Dekrement im Custom-Deleter Callback).
Das mag jetzt etwas umständlicher sein, als sich seinen eigenen Smart-Pointer zu basteln. Der riesen Vorteil wäre aber, dass man einen Standard-Smart-Pointer verwendet, der mit Boost oder TR1 überall verfügbar ist, und keine Dependency zum Resource-Manager oder deinem restlichen Projekt darstellt.
-
Wenn der ResourceManager unbenutzte Ressourcen sofort löschen soll, bietet sich ein
std::(tr1::)weak_ptran.
-
hustbaer schrieb:
* Der Custom-Deleter muss einen shared_ptr auf den Resource-Manager haben, sonst könnte der Resource-Manager gelöscht werden, bevor die letzten Callbacks ausgeführt werden. (Es muss kein shared_ptr sein, aber irgendwie muss man es tracken - intrusive_ptr ginge auch, manuelles Ref-Counting genauso. Und wenn das Resource-Manager statisch instanzierber sein kann, kann man es pimpln, und der Custom-Deleter referenziert direkt die "impl")
oder der Manager laesst sich nicht beenden solange er noch Objekte managed. Erspart massig arbeit - manager Klassen sind idR Singletons und somit ists easy.
Oder aber das Delete braucht eh nicht zwingend den Manager - weil er sowieso selber loeschen kann und sich nur beim Manager vorher unregistern wuerden und wenn der Manager nicht da ist, dann wird eben nicht unregister gemacht.
* Entweder muss sich der Resource-Manager einen weak_ptr auf Objekte behalten, die er schonmal hergegeben hat, damit er bei weiteren Anfragen nach dem selben Objekt einen shared_ptr erzeugen kann der sich den selben "Counter" mit bereits existierenden shared_ptr teilt. Oder er muss für jedes Objekt mitzählen wieviel shared_ptr er hergegeben hat (Inkrement beim "get" und Dekrement im Custom-Deleter Callback).
Sehe ich nicht. Wozu brauche ich dann einen Manager mit shared_ptr? shared_ptr nimmt man, wenn man sich nicht um das Ownership kuemmern will. shared_ptr nehmen und eigenenes Owner Ship management macht diese Probleme die du hier nennst.
Kann man aber einfach umgehen indem man keine shared_ptr hergibt sondern eigene Smartpointer. Wie eben hier den ResourcePtr. Der ist mit Absicht kein shared_ptr (nehme ich mal schwer an).
Dazu muss nur noch die Delete Policy geklaert werden. Mein Liebling ist: fuer jedes Acquire mach ein Release. Das loest alle Probleme hier schlagartig. Wenn man aber ein bisschen Bequem sein will, ist es einfach einen Smartpointer zu schreiben der, wenn der Count auf 0 kommt ein Release macht.
Der Manager kann dann bestimmen was passieren soll. uU will man ja nicht die Resource freigeben nur weil sie nicht mehr aktiv gehalten wird. Oder man gibt sie frei. Dazu muss der Manager nicht mal wirklich clever sein. Acquire / Release. Immer dieses Paar verwenden und der Code wird viel viel einfacher.
Das mag jetzt etwas umständlicher sein, als sich seinen eigenen Smart-Pointer zu basteln. Der riesen Vorteil wäre aber, dass man einen Standard-Smart-Pointer verwendet, der mit Boost oder TR1 überall verfügbar ist, und keine Dependency zum Resource-Manager oder deinem restlichen Projekt darstellt.
Und den Code enorm verkompliziert.
Ne ne. ResourcePtr ist genau das richtige. Nexus braucht nur custom Deleter. Das ist alles.
-
Vielen Dank für die nützlichen Antworten, besonders für die Erklärung, wie mein Problem mit
shared_ptrangegangen werden kann.Werner Salomon schrieb:
Ich verstehe Deine Bedenken nicht. Warum sollte mit dem shared_ptr eine genaue Kontrolle der Lebenszeit nicht möglich sein? Kannst Du vielleicht ein Beispiel nennen.
Ich habe nur gedacht, dass man mit
shared_ptrund Custom-Deleter auch automatische Objekte referenzieren kann. Wenn die dann out of scope gehen, hat man halt Dangling Pointers. Aber das ist wohl ein Anwenderfehler, dem ich nicht allzu viel Beachtung schenken sollte
Shade Of Mine schrieb:
Selbes Konzept kannst du auch hier verwenden. Du erstellst also einen ResourcePtr mit einem eigenen Deleter und kannst so einen eigenen ResourceManager (oder garkeinen) verwenden.
Oder übersehe ich etwas?
Nein, spezifische Deleter scheinen wirklich die beste Lösung zu sein. Ob im
ResourcePtrodershared_ptr, muss ich noch schauen...hustbaer schrieb:
* Der Custom-Deleter muss einen shared_ptr auf den Resource-Manager haben, sonst könnte der Resource-Manager gelöscht werden, bevor die letzten Callbacks ausgeführt werden.
Du würdest es also so lösen, dass Ressourcen ihren Manager am Leben halten, solange sie verwendet werden? Dann müsste man den
ResourceManageraber selbst in einemshared_ptrspeichern. Oder dieResourceManager-Klasse ist nur ein Frontend für den Benutzer, intern wird einshared_ptrauf den tatsächlichen Verwalter gespeichert, der auch nach der Zerstörung des Frontends weiterleben kann.Bisher war die Semantik so: Wenn das
ResourceManager-Objekt stirbt, werden alle Ressourcen freigegeben und allfälligeResourcePtrs werdenNULL– derResourceManagerbesitzt also die Ressourcen. So werden Ressourcen auch garantiert freigegeben, es können keine Leaks (z.B. wegen zyklischer Referenzen) entstehen. Findest du das eher nicht sinnvoll?Ist es mit
shared_ptrüberhaupt möglich mitzuverfolgen, wer alles eine Referenz auf die Ressource hält? Man kann ja Kopien vonshared_ptr-Objekten nicht überwachen, oder? Denn in diesem Fall könnte ich das automatischeNULL-Setzen dershared_ptrs bei der Zerstörung des Managers ohnehin vergessen.hustbaer schrieb:
* Entweder muss sich der Resource-Manager einen weak_ptr auf Objekte behalten, die er schonmal hergegeben hat, damit er bei weiteren Anfragen nach dem selben Objekt einen shared_ptr erzeugen kann der sich den selben "Counter" mit bereits existierenden shared_ptr teilt.
Oder man verwendet hier gleich
shared_ptr. Dann würde die Ressource bei der Strategie "behalte unbenutzte Ressourcen" automatisch allokiert bleiben, und bei "gib unbenutzte frei" könnteOnUnusedResource()denshared_ptrentfernen und damit die Freigabe einleiten. So wie ich das sehe, braucht man wederweak_ptrnoch manuelles Reference Counting, oder?hustbaer schrieb:
Das mag jetzt etwas umständlicher sein, als sich seinen eigenen Smart-Pointer zu basteln. Der riesen Vorteil wäre aber, dass man einen Standard-Smart-Pointer verwendet, der mit Boost oder TR1 überall verfügbar ist, und keine Dependency zum Resource-Manager oder deinem restlichen Projekt darstellt.
Das stimmt schon, so kann man immer mit
shared_ptrarbeiten, egal ob die Ressource im Manager liegt oder sonst irgendwo abgespeichert ist.
-
Shade Of Mine schrieb:
oder der Manager laesst sich nicht beenden solange er noch Objekte managed.
Wie würdest du das umsetzen, wenn man kein Singleton erzwingen möchte? Wäre mein vorhin erwähnter Ansatz des Frontend-Managers halbwegs sinnvoll, oder gibts andere bewährte Möglichkeiten?
Shade Of Mine schrieb:
Kann man aber einfach umgehen indem man keine shared_ptr hergibt sondern eigene Smartpointer. Wie eben hier den ResourcePtr. Der ist mit Absicht kein shared_ptr (nehme ich mal schwer an).
Ja, du erinnerst mich gleich an ein paar Gründe, wieso ich damals nicht
shared_ptrgenommen habe. Einer davon war aber auch, dass ich dessen Fähigkeiten etwas unterschätzt habe. Gut, dass das hier so kontrovers diskutiert wird
Shade Of Mine schrieb:
Dazu muss nur noch die Delete Policy geklaert werden. Mein Liebling ist: fuer jedes Acquire mach ein Release. Das loest alle Probleme hier schlagartig. [...] Acquire / Release. Immer dieses Paar verwenden und der Code wird viel viel einfacher.
Dann würde sich eine Basisklasse mit diesen beiden virtuellen Funktionen wohl anbieten. Oder zwei einzelne Callbacks (z.B.
std::tr1::function). Wobei, ersteres ist einfacher und hat vermutlich weniger Overhead. Statische Polymorphie eignet sich nicht, weil dann die Smart-Pointer-Typen unterschiedlich werden... Oder hast du hier was ganz Anderes gemeint?Shade Of Mine schrieb:
Und den Code enorm verkompliziert.
Ne ne. ResourcePtr ist genau das richtige. Nexus braucht nur custom Deleter. Das ist alles.
Danke für deine Einwände. Mir wäre es schon recht, wenn ich den
ResourcePtrmehr oder weniger behalten könnte. Aber ein paar Nachteile hat das eben auch... Muss ich mir in Ruhe durch den Kopf gehen lassen und überlegen, wie konkrete Implementierungen aussehen könnten
-
Du würdest es also so lösen, dass Ressourcen ihren Manager am Leben halten, solange sie verwendet werden?
Weiss nicht, ich kenne deine genauen Anforderungen nicht. Es gibt hier sehr viele mögliche Kräfte die in die eine oder andere Richtung ziehen können. Es ist auf jeden Fall eine Möglichkeit, und IMO keine grundsätzlich schlechte.
Dann müsste man den ResourceManager aber selbst in einem shared_ptr speichern. Oder die ResourceManager-Klasse ist nur ein Frontend für den Benutzer, intern wird ein shared_ptr auf den tatsächlichen Verwalter gespeichert, der auch nach der Zerstörung des Frontends weiterleben kann.
Richtig. Wobei die Frontend-Variante vermutlich besser ist. Das Frontend könnte z.B. den "Backend" mitteilen wenn es sterben geht. Das Backend könnte daraufhin von "retain" zu "release" wechseln. Oder assert()en wenn es noch referenzierte Resourcen gibt.
Bisher war die Semantik so: Wenn das ResourceManager-Objekt stirbt, werden alle Ressourcen freigegeben und allfällige ResourcePtrs werden NULL – der ResourceManager besitzt also die Ressourcen. So werden Ressourcen auch garantiert freigegeben, es können keine Leaks (z.B. wegen zyklischer Referenzen) entstehen. Findest du das eher nicht sinnvoll?
Ich noch kein solches Problem (Zyklen in Resource-Klassen). Würde ich nicht als sehr wichtig einstufen. Und mit nem assert() checken kann man es ja auch so (s.o.), sofern man vorschreibt dass der User den Manager erst löschen darf, wenn schon alle Resourcen freigegeben wurden.
Wenn du es nicht als Feature brauchst, dass die Resourcen-Referenzen getrackt werden und deterministisch "genullt" werden können, würde ich sagen: vergiss das
Ist es mit shared_ptr überhaupt möglich mitzuverfolgen, wer alles eine Referenz auf die Ressource hält? Man kann ja Kopien von shared_ptr-Objekten nicht überwachen, oder? Denn in diesem Fall könnte ich das automatische NULL-Setzen der shared_ptrs bei der Zerstörung des Managers ohnehin vergessen.
Nein, richtig, richtig.
hustbaer schrieb:
* Entweder muss sich der Resource-Manager einen weak_ptr auf Objekte behalten, die er schonmal hergegeben hat, damit er bei weiteren Anfragen nach dem selben Objekt einen shared_ptr erzeugen kann der sich den selben "Counter" mit bereits existierenden shared_ptr teilt.
Oder man verwendet hier gleich
shared_ptr. Dann würde die Ressource bei der Strategie "behalte unbenutzte Ressourcen" automatisch allokiert bleiben, und bei "gib unbenutzte frei" könnteOnUnusedResource()denshared_ptrentfernen und damit die Freigabe einleiten. So wie ich das sehe, braucht man wederweak_ptrnoch manuelles Reference Counting, oder?Da muss ich jetzt etwas ausholen.
Ein shared_ptr ist besteht im Prinzip aus:
* einem rohen zeiger auf *irgendwas* (*)
* einer "shared reference" auf ein Objekt dass ich hier mal "Counter" nenne
Der "Counter" besteht wiederum aus:
* einem Zähler der die shared_ptr zählt die auf ihn zeigen
* einem rohen zeiger
* einem Deleter
Einen shared_ptr erzeugen, der einen bereits bestehenden "Counter" verwendet, kann man nur, wenn man einen anderen shared_ptr oder weak_ptr hat, der diesen "Counter" verwendet.Und der Deleter wird erst ausgeführt, wenn der letzte shared_ptr verschwindet, der diesen "Counter" verwendet.
Wenn du jetzt im Manager einen shared_ptr hältst, und alle shared_ptr den selben Counter verwenden, dann wird der Deleter niemals ausgeführt. Da der Manager ja immer noch einen shared_ptr mit diesem Counter hat. Und der Manager könnte auch nie sterben, da die Resource selbst einen shared_ptr auf den Manager hat -> Cycle -> Leak
Wenn du im Manager dagegen weder einen shared_ptr noch einen weak_ptr hast, dann kannst du keine neuen shared_ptr erzeugen, die den selben Counter verwenden. Jeder "ausgegebene" shared_ptr hätte dann seinen eigenen Counter, und der Deleter würde mehrfach ausgeführt (1x pro Counter, und das erste mal u.U. viel zu früh). In der Variante müsstest du dann im Manager also mizzählen wie viele "Deleter Ausführungen" noch "offen" sind.
Ohne mitzhälen geht also nicht mit shared_ptr, und auch nicht ohne shared_ptr/weak_ptr. Muss also ein weak_ptr her, anders geht es nicht.
----
*: Es können verschiedene shared_ptr, die den selben "Counter" verwenden, ganz unterschiedliche "rohe Zeiger" speichern. Das ermöglicht u.A. shared_ptr zu casten, aber auch shared_ptr auf "innere" Objekte (z.B. Membervariablen) zu erstellen, die trotzdem das ganze Objekt am Leben halten. Im Prinzip kann man auch eine beliebige gültige Adresse übergeben.
----
Ein paar weitere Gedanken:
Zeiger auf Null zu setzen, wenn das Manager stirbt, ist nicht ohne grösseren Aufwand (Implementierung + Runtime-Overhead) möglich, wenn es threadsafe sein soll. Ausserdem kannst du Fälle nicht abdecken, in denen jmd. z.B. sowas macht (auch ohne multithreading):
void DoStuff(Image* p) { p->Blah(); DoSomeThing(); // <- und hier gibt das Programm den Manager frei (warum auch immer) p->Blubb(); // und hier knallts dann. oder auch nicht. UB halt. } void FunFunFun(MyPtr<Image> p) { DoStuff(p.get()); // Wir halten ja "p" während DoStuff() läuft, also ist alles gut // (bloss eben leider doch nicht) }Statt zeiger Null zu setzen, könntest du auch die gemanagten Objekte in einen "leer" Zustand versetzen, ähnlich Dispose()/Close() in C#/Java. Wenn das threadsafe gehen soll ist das zwar auch einiges an Aufwand, aber weniger als beim "getrackte Zeiger Nullsetzen" Variante. Und der Oben gezeigte Problemfall lässt sich auch lösen.
Nachteile: man braucht einen (mir immer sehr lästigen) Zombie-Zustand für alle Resource Klassen.
Und wenn Multithreading ein Thema ist, müssen alle Klassen intern synchronisiert sein, damit keiner Dispose() sagen kann, während auf einem anderen Thread z.B. gerade eine andere Memberfunktion ausgeführt wird.----
Nochwas z.T. acquire/release (oder im COM jargon: AddRef/Release). Finde ich auch nicht übel. Wobei ich mir dann denke: wieso überhaupt mit Custom Deleter rummachen? In so einem Fall kann das Objekt selbst wissen, ob es sich beim "final release" löschen soll, oder doch nicht (weil es z.B. aufm Stack lebt).
Dann braucht man weder eigene Smart-Pointer, noch muss man die Interfaces der Resourcen-Verwender-Klassen mit Smart-Pointern zumüllen. Der passende Smart-Pointer heisst dann boost::intrusive_ptr, und die Resourcen-Verwender-Klassen können sich jederzeit aus einem übergebenen rohen Zeiger einen neuen, funktionierenden boost::intrusive_ptr basteln, wenn sie das Objekt am Leben erhalten wollen.
Die Verwendung von Resourcen kann dann einfach so aussehen:void foo() { Resource res; bar(&res); boost::intrusive_ptr<Resource> countedRes(new Resource(counted_tag)); // oder auch über ne abgeleitete TrackedResource Klasse - was auch immer bar(countedRes.get()); }
-
hustbaer schrieb:
Wenn du jetzt im Manager einen shared_ptr hältst, und alle shared_ptr den selben Counter verwenden, dann wird der Deleter niemals ausgeführt. Da der Manager ja immer noch einen shared_ptr mit diesem Counter hat.
Stimmt, das war ein blöder Denkfehler meinerseits.
hustbaer schrieb:
Wobei ich mir dann denke: wieso überhaupt mit Custom Deleter rummachen? In so einem Fall kann das Objekt selbst wissen, ob es sich beim "final release" löschen soll, oder doch nicht (weil es z.B. aufm Stack lebt).
Dann müsste ich aber die Anforderungen an die Ressourcenklassen erhöhen, die momentan recht niedrig sind. Ein paar bereits existierende Klassen könnten so nicht mehr direkt verwendet werden. Aber die Möglichkeit mit
intrusive_ptrsieht grundsätzlich sehr interessant aus, könnte ich mir für später merken.Danke auch für all die anderen Hinweise, auf die ich jetzt nicht eingegangen bin.
-
Nexus schrieb:
Shade Of Mine schrieb:
oder der Manager laesst sich nicht beenden solange er noch Objekte managed.
Wie würdest du das umsetzen, wenn man kein Singleton erzwingen möchte? Wäre mein vorhin erwähnter Ansatz des Frontend-Managers halbwegs sinnvoll, oder gibts andere bewährte Möglichkeiten?
Prinzipiell sind Manager Klassen ja nicht da um sie zu erstellen und vernichten wie man lustig ist. zB koennte der Manager einfach eine Exception werfen wenn man ihn beenden will obwohl er noch Objekte besitzt. Alternativ kann er natuerlich auch brutal alle Objekte killen :p
Andererseits ist die Frage: braucht man sowas ueberhaupt. Der Manager ist ja eher nur da um Resourcen zu beschaffen. Muss er von ihrer Vernichtung ueberhaupt in Kentniss gesetzt werden? Die Smartpointer koennen ja die Resourcen selber loeschen...
Meine Hauptfrage ist eigentlich: was genau machen die Manager Klassen? Allokieren sie nur, passen sie auf dass eine Resource nur einmal geladen wird,... Was genau ist ihr Vorteil gegenueber einer einfachen acquire_resource() Funktion?
-
Shade Of Mine schrieb:
zB koennte der Manager einfach eine Exception werfen wenn man ihn beenden will obwohl er noch Objekte besitzt.
Dann dürfte der Manager sich im Destruktor aber nicht selbst beenden, weil sonst Exceptions während der Aufräumphase fliegen.
Shade Of Mine schrieb:
Meine Hauptfrage ist eigentlich: was genau machen die Manager Klassen? Allokieren sie nur, passen sie auf dass eine Resource nur einmal geladen wird,... Was genau ist ihr Vorteil gegenueber einer einfachen acquire_resource() Funktion?
Der Manager soll schauen, ob eine bestimmte Ressource schon mal geladen wurde, und sie in diesem Fall nicht erneut laden. Im Prinzip also ein Cache.
-
Ich habe vorläufig folgendes Design implementiert:
ResourceManagergibtstd::tr1::shared_ptrauf Ressourcen zurück (unter Verwendung eines eigenen Deleters)- Ressourcen leben solange es
shared_ptrs auf sie gibt, kein Tracking der Referenzen mehr ResourceManagerkann aktivshared_ptrauf Ressourcen halten, um Freigabe bei Nichtbenutzung zu verhindern- Ansonsten hält
ResourceManagereinenweak_ptrauf bereits allokierte Ressourcen, um bei erneuter Anfrage einenshared_ptrzu liefern
Das ist soweit okay, vor allem wird die Verwendung eines standardisierten Smart-Pointers ermöglicht. Falls ich in Zukunft jedoch plötzlich etwas implementieren will, das mit
shared_ptrnicht geht (z.B. Referenztracking, wenn auch nur für Debug-Zwecke), habe ich komplett verloren. Von daher bin ich mir nicht sicher, ob die Entfernung desResourcePtr-Klassentemplates eine gute Entscheidung ist, oder ob sich die von Shade Of Mine vorgeschlagenen Custom Deleter im Endeffekt doch mehr lohnen. Was meint ihr?Kennt jemand Beispiele für ähnliche Ressourcenverwaltungssysteme, z.B. aus irgendeiner Game-Engine oder so?
-
War wohl nicht allzu klug, während einer Post-Flut zu antworten

Hätte jemand eine Idee hierzu? Oder Verweise auf ähnliche Designs?
-
Ich möchte den Thread jetzt nicht unnötig wieder ins Rampenlicht bringen, aber mich würde interessieren, ob Du "Beispiele für ähnliche Ressourcenverwaltungssysteme, z.B. aus irgendeiner Game-Engine oder so" gefunden hast.
-
Ich suchte damals noch ein wenig, aber die Ergebnisse waren nicht allzu brauchbar. Oft handelte es sich um allgemeine OOP-Designs à la Java, wo sowieso andere Probleme bestehen. Bei den C++-Implementierungen habe ich eigentlich nur Sachen gefunden, auf die man auch selbst kommt -- nur viel schlechter implementiert (oft mit rohen Zeigern und antikem Codestil). Ich habe aber auch nicht wahnsinnig lange gesucht, wahrscheinlich würde man schon was finden.
-
Alles klar, danke!
