雖然 Lua C API 中有提供弱參照,但 Lua 語言本身並沒有提供標準支援。此備註提出 Lua 中弱參照的介面,說明實作方式,並提供一些實際使用範例:表格物件的安全解構函式和物件快取。
-- creation ref = weakref(obj) -- dereference obj = ref()也就是說,使用一個名為「weakref」的新全域函式來建立物件的弱參照。弱參照可以使用函式呼叫運算子來解除參照。如果解除參照傳回 nil,表示物件已被垃圾回收。由於 nil 有這個特殊意義,因此不允許對 nil 物件本身建立弱參照。
我們的 weakref 函式需要呼叫 lua_ref(),並傳回持有結果參照 ID 的物件。解除參照,使用參照物件上的函式呼叫標籤方法實作,只需呼叫 lua_getref()。最後,當參照物件本身被回收時,需要釋放參照,因此使用垃圾回收 (gc) 標籤方法來呼叫 lua_unref()。
使用者資料類型是參照物件的自然選擇,因為它是唯一提供 gc 事件的類型。此外,由於只需要一個整數,因此可以將狀態儲存在使用者資料指標本身,這樣就不需要動態記憶體配置。
此實作的原始碼提供為官方 Lua 4.0 發行版的修補程式,可在此處取得 here。使用修補程式工具套用,如下所示
cd <lua distrubution directory> patch -p1 < weakrefs.patch此修補程式包括測試目錄「weakref.lua」的新增內容,其中顯示此擴充功能的一個簡單範例。
建議將此實作新增至 Lua 的「baselib」標準函式庫,原因有數:弱參照具有普遍的實用性;實作簡單,且已由 C API 支援;而且由於只需要一個新的 Lua 函式,因此為其目的建立獨立的函式庫將會過於繁瑣。
然而,在某些情況下,物件會擁有不會自動釋放的系統資源,例如檔案句柄、圖形緩衝區等。一個有點繁瑣的解決方案是使用 userdata 類型以 C 實作此類物件,而此類型確實有 gc 事件。弱參照提供了優雅的替代方案,讓 Lua 可以輕鬆地為表格物件進行安全的垃圾回收事件。
實作使用包含弱參照/解構函式配對的表格。當參照的物件被收集時,將呼叫對應的解構函式。這些解構函式是安全的,因為它們無法存取正在被銷毀的物件。解構函式所需的任何資訊(例如資源句柄)必須可以獨立於物件存取。由於具有一等函式物件,這對 Lua 來說相當輕鬆。
需要一個小型介面來管理表格,其中包含一個將解構函式繫結至物件的函式,以及一個檢查已收集物件的函式。以下是 Lua 的實作
------------------------------------------ -- Destructor manager local destructor_table = { } function RegisterDestructor(obj, destructor) %destructor_table[weakref(obj)] = destructor end function CheckDestructors() local delete_list = { } for objref, destructor in %destructor_table do if not objref() then destructor() tinsert(delete_list, objref) end end for i = 1, getn(delete_list) do %destructor_table[delete_list[i]] = nil end end與其在某個時間間隔手動呼叫 CheckDestructors(),自然的做法是將其連結至 Lua 的垃圾回收週期。Lua 虛擬機器會在週期結束時呼叫 nil 類型的 gc 標籤方法來支援此功能。
作為安全解構函式使用的範例,請考慮一個用於將程式訊息記錄至檔案的物件。當物件被垃圾回收時,我們希望記錄檔可以關閉。(此範例很簡單,因為檔案句柄會在程式終止時關閉。然而,此方法可以輕鬆套用至其他類型的資源。)
------------------------------------------ -- example object using safe destructor function make_logobj(filename) local id = openfile(filename, "w") assert(id) local obj = { file = id, write = function(self, message) write(self.file, message) end, } local destructor = function() closefile(%id) end RegisterDestructor(obj, destructor) return obj end
一種補救方法是僅快取最近存取的 n 個網頁,但這並未考量資料大小,因此無法有效利用可用的記憶體。一種改良的方法是快取最近存取的 x 千位元組已產生資料。除了增加程式複雜度之外,這裡出現的問題是找出 x 的適當值。這類似垃圾收集器所面臨的問題:循環應該多久執行一次,以及在使用多少記憶體後執行?
透過使用弱參照進行快取,程式複雜度得以降低,同時將記憶體使用問題交由垃圾收集器處理。快取表格並非儲存已產生的網頁物件,而是包含這些物件的弱參照。當垃圾收集循環執行時,目前未使用的網頁物件將會被收集。
以下是一個實作範例,假設有一個函式 GeneratePage(),它會根據「名稱」產生一個網頁物件。函式 CleanCache() 用於移除已收集物件的表格項目,它也應該鏈結到 Lua 的 gc 循環。
------------------------------------------ -- Page cache local cache_table = { } function GetPage(name) local ref = %cache_table[name] local obj = ref and ref() if not obj then obj = GeneratePage(name) %cache_table[name] = weakref(obj) end return obj end function CleanCache() local delete_list = { } for name, ref in %cache_table do if not ref() then tinsert(delete_list, name) end end for i = 1, getn(delete_list) do %cache_table[delete_list[i]] = nil end end