Folgendes Szenario: In meinem Framework will ich den aktuellen Spielzustand in Savegames speichern und später wieder laden. Ein wichtiger Teil des Spielzustandes sind Entities, generische Objekte in der Spielwelt die aus verschiedenen Component-Objekten bestehen. Nun braucht man ab und zu, beispielsweise in einer dieser Komponenten, einen Zeiger auf andere Spielobjekte, z.B. ein Ziel, das ich gerade angreife. Entities können zerstört werden, wodurch Zeiger auf sie ungültig werden.
Zur Laufzeit wird dieses Problem durch weak_ptr gelöst. Diese bieten die Möglichkeit, bzw. den Zwang, die Gültigkeit eines Zeigers mit überschaubaren Kosten zu überprüfen und sind sehr nett zu benutzen. (Ich halte diese Variante für wesentlich robuster und einfacher als sich alle Objekte zu merken, die auf einen zeigen, und diese vor der Zerstörung zu benachrichtigen.) Soweit alles gut.
Mein Speichersystem ist momentan über eine Archive-Klasse implementiert, die für verschiedene Typen und Aggregate (z.B. ein std::vector eines andern Typs) Lade- und Speicherfunktionen bietet. Mein reicht also ein Archive-Objekt einmal überall rum, dabei schreibt z.B. ein Entity all seine Werte hinein und reicht es dann an seine Komponenten weiter, damit diese ihre Werte in das Archive schreiben. Das funktioniert soweit auch zufriedenstellend.
Aber was mache ich nun mit meinen weak_ptr'n? Man könnte damit anfangen, allen Entities (ich brauche denke ich zunächst nur Zeiger auf Entities) eine fortlaufende ID zu geben, und statt dem Zeiger dann diese ID zu speichern, die man nach dem Laden wieder benutzen kann um die Speicheradresse zu erfragen. Idealerweise sollte dieser Lookup natürlich nur einmal geschehen. Allerdings kann man das nicht direkt tun, es kann ja sein, dass Objekt A vorne in der Datei steht und auf Objekt B zeigt, dass weiter hinten ist - die ID kann also erst aufgelöst werden, nachdem alle Entities geladen wurden.
Mit fallen spontan drei Lösungen ein:
- Ich könnte jetzt analog zur Ladefunktion jedem Objekt für Phase 2 eine weitere Ladefunktion geben. In den allermeisten Fällen wird diese leer sein, bzw. nur die entsprechende Funktion aller Unterklassen aufrufen. Viel nervige Arbeit und potentiell anfällig für Fehler.
- Ich ersetze meine weak_ptr durch ein Objekt, dass entweder eine ID oder einen weak_ptr speichert. Beim ersten Dereferenzieren wird der ID-Lookup vorgenommen. Der weak_ptr kommt schon immer mit dem Existenztest (aber diesen Preis muss man wohl zahlen), aber immer einen zweiten Test durchführen zu müssen hört sich nicht soo elegant an (ggf. könnte man den Test auch durch ein Umbiegen von Funktionszeigern ersetzen - wäre das dann toll?)
- Beim Laden registriert man eine Callback-Funktion (z.B. ein Lambda). Am Ende des Ladevorgangs kann das Archive bzw. der SceneManager dann diese CallbackListe abarbeiten und die Zeiger entsprechend setzen. Einzige Voraussetzung dabei wäre, dass sich während dem Laden die Adresse des weak_ptrs nicht ändern darf (damit der Callback gültig bleibt), aber es ist schwer sich einen Fall vorzustellen, bie dem dies nicht so sein sollte.