Schwache Zeiger
Wenn man versucht, nur mit std::shared_ptr
zu arbeiten, dass stößt man früher oder später auf ein Problem: kreisförmige Objektbeziehungen machen unsere schöne, neue, automatisch aufräumende Welt wieder kaputt. Referenzieren sich zwei oder mehr Objekte im Kreis, dann können die über ihre shared_ptr
selbst dann nicht gelöscht werden, wenn sich außerhalb des Kreises keiner mehr dafür interessiert. Um diese Kreise zu unterbrechen und trotzdem die Annehmlichkeiten eines Smart Pointers zu haben, bietet die Standardbibliothek std::weak_ptr
.
Video
Code
|
|
Erklärung
Der anonyme Block in Zeile 28 bis 41 dient hier der Verdeutlichung des Prinzips std::weak_ptr
. Der eigentliche
weak_ptr w
lebt länger als alle shared_ptr
innerhalb dieses Blocks. Wäre w ein klassischer shared_ptr
, dann würde die
Zuweisung in Zeile 33 den Referenzzähler des Pointer um eins erhöhen und das Test-Objekt würde den Block überleben. Das
wollen wir aber nicht. w
soll uns zwar die Möglichkeit geben, auf das entsprechende Objekt zuzugreifen, aber nur dann,
wenn der eigentliche “Aufhängepunkt”, der zugehörige shared_ptr
noch nicht vernichtet wurde. Zu diesem Zweck können wir
w
in Zeile 35 mittels w.expired()
fragen, ob der dahinter stehende shared_ptr
noch gültig ist. Ist das der Fall, so
können wir eine neue Kopie dieses Pointer mittels w.lock()
erhalten (Zeile 37), normal benutzen und dann wieder vernichten.
Fällt nun am Ende des Blocks der shared_ptr t
aus dem Scope und damit dessen Referenzzähler auf 0, so wird das Test-Objekt
freigegeben (Zeile 41). Wenn wir danach den weak_ptr
fragen, ob er noch gültig ist, dann wird der das verneinen.
Wie kann man das nun nutzen, um kreisförmige Beziehungen zu bauen? Nehmen wir an, wir haben ein Objekt A, welches eine
Referenz auf ein anderes Objekt B hat. B hat ebenso eine Rückreferenz zu A. Sind beide Referenzen mittels shared_ptr
implementiert, dann entsteht das bekannte Problem. Implementieren wir hingegen B->A als weak_ptr
und A->B als shared_ptr
,
so bleiben beide Objekte nur dann erhalten, solange es noch eine Referenz von außen auf A gibt. Ist das nicht mehr der Fall,
dann wird A vernichtet, damit auch A->B, weswegen B ebenfalls freigegeben wird. Dadurch, das B->A ein weak_ptr
ist, zählt
er nicht für die Lebenszeit des Objektes.
Durch diesen Entwurf entsteht natürlich eine gewisse Hierarchie zwischen den Objekten. B wird als abhängiges Objekt in A
geführt, umgekehrt aber nicht. In den allermeisten Fällen ist das auch genau das richtige Design. Sollten beide Objekte
gleichberechtigt sein, dann wird’s komplizierter. Entweder nimmt man nur weak_ptr
zwischen beiden Objekten und macht sie
damit faktisch unabhängig voneinander oder man arbeitet mit zwei shared_ptr
und muss dann selbst für das Auftrennen des
Kreises sorgen (bspw. indem man einen der shared_ptr
mittels .reset()
zurücksetzt). In dem Fall wäre es aber durchaus
nochmal empfehlenswert, das eigene Design zu überdenken.