shared pointer 和 weak pointer
全部程式都用 shared pointer 存物件的話,可以避免用到 dangle pointer 造成程式掛掉。特別是有用 event loop 時,用 shared pointer 比較不會出錯。但是用 shared pointer 得處理其它問題:
- 因 circular reference 造成 memory leak。
- 刪除物件的時機不明確。
如果不會在 destructor 作複雜的事,第二點到是無所謂。像 Java 和 Python 就不保證呼叫 “destructor” (兩者用不同名稱表示) 的時機,因此最佳實踐都建議不要用,通常也沒人用。另一方面,第一點會影響到軟體能否常期穩定執行,需要處理。
circular reference 其實比想像中來得容易發生,比方說 observer pattern 裡面的 Subject 需要保留 Observer 的指標,如果 Observer 也有保留 Subject 的指標,就會造成 circular reference (雙方都用 shared_ptr 的情況)。
有沒有可能讓 Subject 僅保留 Observer 的指標,但不影響 Observer 的 reference count?並且在 Observer 被刪除後,能判斷 Observer 已無法再使用?
weak pointer 就是為此而生的。概念如此,不過各家實作的方式不同,效能和使用限制也不同。
Java
Java 的 reference 可多了,見官方文件,以及《Java引用总结 — StrongReference、SoftReference、WeakReference、PhantomReference》附的測試程式碼和該文最後的摘要。
WeakReference 的介面設計如下:
- constructor 收目標物件直接產生 WeakReference 物件。
- 使用前先用 get() 取得目標物件 (傳回的物件會影響 reference count),再使用傳回的物件。如果物件已被刪除,get() 會回傳 null。
- get() 是 thread-safe 。文件上沒有討論這點,不過 JVM 要 GC 前會先 stop-the-world,應該不用擔心。
C++11 weak_ptr
使用方式和 Java 差不多:
- 用 operator= 或 constructor 收 shared_ptr 產生 weak_ptr。
- 使用前用 lock() 取得 shared_ptr。
- 依文件和這篇所言,lock() 是 atomic,所以是 thread-safe。
透過 atomic operation 而不是 lock object,效率會好很多。有稍微看一下 GCC 的實作,不過有點複雜,沒有參透全部。有機會再仔細研究吧。
Chromium weak_ptr
使用方式和前兩者不同,WeakPtr 的介面如下:
- class Foo 需要存在 WeakPtr 內的話,需要新增一個 member WeakPtrFactory (建議放在 member field 的最後一個,這樣它的 destructor 會最先被呼叫,提早無效化發出去的 WeakPtr),或繼承 SupportsWeakPtr。關鍵是 Foo 要有 internal::WeakReferenceOwner 管理 WeakReference,WeakPtr 會用 WeakReference 檢查目標物件是否仍能使用。
- 以使用 WeakPtrFactory 的方式為例,用 WeakPtrFactory::GetWeakPtr() 產生 WeakPtr。
- 使用 WeakPtr 方式和使用一般指標一樣,使用前先檢查 WeakPtr 是否為 null,不是 null 再存取它的 member fields/functions。
- 粗略來說,WeakPtr 是 non-thread-safe。嚴格地說,WeakPtr 可以跨 thread 執行,但不能同時在多個 thread 存取它。要仔細說明的話,得了解 Chromium 定的一些 API [*1],這裡就略過了。
以型別 T 的物件使用 WeakPtrFactory 為例,下面是 WeakPtr 的類別關聯圖。
比較
Chromium 的設計和 Java 以及 C++11 std 不同,需要 weak pointer 的 class 要在定義 class 時就作好配套措施,又需留意 thread safety。但是不會另外生出 shared pointer,效率比較好。
一般來說,有 thread safety 的問題就得用 lock 保護,因此會降低效率。不過 Chromium 許多物件的設計是限制在同一個 thread 上執行,設計上就不允許跨 thread 執行。所以 WeakPtr 這樣設計到也不會太突兀。