Im ersten Teil dieses Artikels beschrieb ich das einfache Zählen der Referenzen, mit der man unreferenzierte Objekte erkennen und löschen kann. Objekte können sich jedoch zyklisch referenzieren und so kann es referenzierte aber dennoch unerreichbare Objekte geben, die durch einfaches Zählen der Referenzen nicht zu entdecken sind.
In diesem Teil beschreibe ich, wie man die zyklischen Referenzen in bestimmten Situationen vermeiden kann und so das Problem umgehen kann.
| Inhaltsverzeichnis |
|---|
| 1. Nicht alle Pointer sind gleich |
| 2. Hierarchie |
| 3. Dynamische Aggregate |
| 4. Einschränkungen |
1. Nicht alle Pointer sind gleichOft ergeben sich zyklischen Referenzen aus der Notwenigkeit, Teile einer hierarchischen Struktur miteinander in beide Richtungen zu verknüpfen. Eine Bestellung kann z.B. auf ihre Posten zeigen, die Posten selbst können wiederum auf die Bestellung zeigen. Ein Verzeichnis kann auf die darin enthaltenen Unterverzeichnisse zeigen, die Unterverzeichnisse wiederum auf deren Elterverzeichnis.
Wenn wir alle diese Beziehungen mit den referenzzählenden Zeigern RefCountPtr abbilden, entstehen referenzzyklen von Objekten, die zwar unerreichbar sind, aber dennoch ihre Anzahl von Referenzen nie 0 erreicht.
Wie kann damit unsere Garbage Collection umgehen? Noch gar nicht. Wir müssen ihr dabei helfen, indem wir die Struktur so organisieren, dass wir die Referenzzyklen vermeiden werden.
Bleiben wir bei unserem Beispiel mit der Bestellung: Wir entwickeln eine Anwendung, die Bestellungen aus einer Datenbank laden kann, und sie in einem Fenster darstellen kann. Jeder Posten der Bestellung wird in einer Zeile einer Liste dargestellt. Wenn der Benutzer auf einen Posten klickt, wird ein anderes Fenster geöffnet, in dem man Detailinformationen über den Posten sehen kann und zu anderen Posten navigieren kann. Alle Fenster können unabhängig voneinander geschlossen werden. Die geladene Bestellung soll aus dem Speicher gelöscht werden, wenn kein Fenster sie selbst oder einen ihrer Posten zeigt.
Wir können also weder die Bestellung noch einen ihrer Posten löschen, solange nur ein Fenster auf ist, dass sie ganz oder nur teilweise anzeigt.
2. HierarchieUnsere Struktur ist also ein Aggregat, dass aus einem Objekt, welches „das Ganze“ repräsentiert und Objekten, welche Teile des Ganzen sind, besteht. Das Aggregat kann und soll ganz gelöscht werden, wenn kein Pointer „von außen“ darauf oder „darein“ zeigt.
Um dies zu erreichen, muss das Aggregat muss also einen gemeinsamen Referenzzähler verwalten, der von allen RefContPtr, die auf ein Teil des Aggregates oder das Aggregat selbst zeigen, inkrementier bzw. dekrementiert wird. Außerdem dürfen die Beziehungen zwischen den Teilen des Aggregates den Referenzzähler nicht beeinflussen – die Bestellung braucht und darf nicht auf ihre Posten mit RefContPtr zeigen, und die Posten brauchen und dürfen mit keinem RefContPtr auf die Bestellung zeigen.
Zurzeit benutzt jedes RefCounted-Objekt einen eigenen Referenzzähler. Wir können die Klasse so erweitern, dass die Exemplare, die Teile eines Aggregates sind, statt des eigenen Zählers den von deren Aggregat verwenden: Quelltext 3
Zuerst machen wir die Methode addRef und release in der Basisklasse virtuell, damit wir sie überschreiben können. Wird ein Exemplar von RefCountedPart als Teil eines Aggregates erstellt, so benutzt es statt des eigenen Referenzzählers den des Aggregates. Das Aggregat selbst kann wiederum Teil eines größeren Aggregates sein.
Mit dieser Konstruktion haben wir erreicht, dass alle RefCountPtr, die in das Aggregat zeigen, dessen Referenzzähler verwenden. Man kann also sagen, dass die Teile das Aggregat vor dem Löschen durch die Garbage Collection beschützen. Andersherum muss das Aggregat die Verantwortung dafür übernehmen, dass die Teile des Aggregates gelöscht werden, wenn es selbst gelöscht wird.
Da das Löschen der Teile des Aggregates nicht die Garbage Collection übernimmt, gelten für sie auch nicht alle Einschränkungen, die für die selbständigen RefCounted-Objekte gelten. Die Teile dürfen z.B. Membervariablen des Aggregates sein.
Wie schon bei RefCounted müssen wir darauf achten, dass die Beziehung des Teiles zum Aggregat nicht zum eigentlichen Zustand des Teiles gehört, und deswegen nicht kopiert werden darf.
3. Dynamische AggregateManchmal kann es nützlich sein, wenn das Aggregat erkennen kann, ob RefCountPtr auf ein von seinen Teilen zeigt. Das Aggregat, dass die Verantwortung für das Löschen der Teile trägt, kann dann entscheiden, bestimmte, „von außern“ nicht referenzierte Teile, zu löschen – auch wenn es selbst nicht gelöscht wird.
Mit einer einfachen Modifikation können wir auch diese Funktionalität bieten. Die Teile behalten ihren eigenen Referenzzähler, und inkrementieren den des Aggregates nur einmal, wenn der eigene größer 0 wird. Fällt er wieder auf 0 zurück, wird der Referenzzähler des Aggregates dekrementiert. Das Aggregat kann sogar einen Teil von sich lösen, und es zu einem selbständigen Objekt machen, wenn es mit new angelegt worden ist. Hier ist der angepasste Quelltext 4.
4. EinschränkungenDurch die hierarchische Referenzzählung haben wir das Problem der zyklischen Referenzen nicht gelöst, nur umgangen. Diese Lösung ist nur dann geeignet, wenn ein Aggregat von mehreren Objekten zusammen gelöscht werden muss und wenn keine Pointer mehr „in“ das Aggregat zeigen.
Wie man mit echten zyklischen Referenzen umgehen kann, beschreibe ich im Teil 3.
Im Teil 1 definiere ich die Grundbegriffe und beschreibe die einfache Referenzzählung.
Hier im Teil 2 beschreibe ich die hierarchische Referenzählung.
Im Teil 3 schließlich die Erkennung von Zyklen.
Die Bibliothek ist aber bereits jetzt komplett.
Die C++-Quelltexte habe ich mit diesem netten Tool von
C++ zu HTML konvertiert.