連結標量全域變數 two |
|
底下是一個解法,允許您將全域變數設定成建構函式的結果。請參 BoundScalarGlobalsOne 來看不同的實作。
免責聲明:我負責撰寫這個東西,但我不會在我的程式碼中使用它,不是因為它很慢,而是因為它太奇怪了。最後,使用 getter 和 setter 函式很簡單。然而,我把它放在這裡,因為我認為它是很棒的後設方法可能性範例,且說明了在沒有 getable 和 settable 後設方法的情況下解決問題是有用的方式。這在建構 C 結構的代理程式時很有用。
要執行這個,您需要至少將已知元表格與每種不同的 C 物件相關聯。這個元表格實際上是物件「類型」。元表格必須有值為(可能是 C)函式的 key __get 和 __set,用來取得和設定 C 標量的值。如果 __get 是同一函式,則沒有任何事項可以阻止您對類型/後設方法表格填入其他後設方法,例如算術或比較器,甚至是 __call、__index 或 __newindex。
建構函式需要將 C 物件與元表格相關聯,且必須使用 register_type 來註冊元表格(如下所示)。(這個機制與 lauxlib 中的機制沒有很大不同,從我寫它到現在已經兩年了。)
沒有必要使用 C 和使用者資料,下方的範例使用 Lua 函式和表格,但希望 C 介面合理明顯。
一旦建構函式的結果被指定給全域變數,未來設定和取得該全域變數將遞延至 __get 和 __set 偽後設方法,這樣您就永遠無法擺脫連結(好吧,可能會有解除連結函式,但我沒有寫那個。)
在 BoundScalarGlobalsOne 中,我試著處理有現有元表格的全域變數表格。這一次我沒有,儘管相同的策略在這個案例中可行,這只是讓程式碼更難追蹤,而它本身就很夠難追蹤了 :)
為了有效率,我假設您不會想要重新定義現有的全域變數為 C 值,且執行這個程序後不會有很多新的非 C 值全域變數。那表示系統函式庫函式仍會在沒有干擾的情況下被查詢。
-- BoundScalar2 -- Version 0.1 -- RiciLake -- do local types = {} -- table of metatables we're going to deal with local global_meta = {} -- metatable for globals local sub_meta = {} -- metatable for new globals local c_proxied = {} -- table of proxied values. local new_globals = {} -- normal globals -- use this function to register your metatables (types) function register_type(meta) types[meta] = true end -- __index metamethod for globals table -- -- We know the key doesn't exist in the globals table, -- which contains pre-existing globals. It -- might be new global, or it might be a proxied c-value. -- We defer the lookup to the new_globals -- table which has its own __index metamethod function global_meta.__index(t, k) return new_globals[k] end -- __index metamethod for new_globals table -- -- This gets invoked if the key wasn't found in the -- new_globals table. So it is either a -- proxied c-value or an undefined global function sub_meta.__index(t, k) local val = c_proxied[k] if val then return getmetatable(val).__get(val) end end -- __newindex metamethod for globals table -- -- With set, we need to follow a different strategy, because we need to -- intercept every attempt to set a global (this doesn't happen -- with pre-existing globals). function global_meta.__newindex(t, k, v) -- first we see if we're proxying this key, and if so we just pass on -- the value. c_proxied does not have metamethods, so we don't need -- rawget. local cval = c_proxied[k] if cval then getmetatable(cval).__set(cval, v) else -- get the *value*'s type (well, metatable) -- and see if it is one of ours. local meta = getmetatable(v) if meta and types[meta] then -- It is a c_val, so we need to get rid of it from regular -- globals, in case it exists, and put it into the proxied -- table new_globals[k] = nil c_proxied[k] = v else -- it is neither an existing c_value nor a new one. So we just -- stash it away new_globals[k] = v end end end setmetatable(getfenv(), global_meta) setmetatable(new_globals, sub_meta) end
現在我們可以定義我們的可繫結類型。以下是快速範例。參閱範例輸出以了解其運作方式
do local date_meta = {} register_type(date_meta) -- __get returns the date according to the object's format function date_meta.__get(date_obj) return os.date(date_obj.fmt) end -- __set changes the object's format function date_meta.__set(date_obj, fmt) date_obj.fmt = fmt end -- constructor function Date(fmt) return setmetatable({fmt = fmt or "%c"}, date_meta) end end
範例輸出
> now = Date() > print(now) Thu Jan 16 16:41:13 2003 > gmt = Date("!%c") > print(gmt) Thu Jan 16 21:41:22 2003 > gmt = "!%F %R" > print(gmt) 2003-01-16 21:41