使用者資料環境範例

lua-users home
wiki

以下是允許使用環境資料表做為「依需求」儲存未知金鑰的範例程式碼,我已經習慣這麼做,因為這樣能讓 Lua 程式新增屬性至以完整使用者資料方式實作的 C 物件。該程式碼從 quick&dirty 向量函式庫中萃取,可以從 [1][2] 下載,範例輸出則在 [3]。我(RiciLake)昨晚才隨興湊合在一起,所以沒保證或怎樣。

以下為基本使用者資料結構

// How do you get the wiki to syntax colour C?
typedef struct lvec_s {
#ifdef VERSION51
  int has_env;
#else
  int len;
#endif
  lua_Number d[1];
} lvec_t;
請注意 has_env 成員的使用。除了在 x86 架構上將使用者資料標頭填補為雙字邊界(避免雙倍精度浮點數跨越快取邊界)之外,這也會用作旗標,表示物件是否有有用的環境資料表。(在版本 5.0 中,len 成員會引發定位錯誤。最好使用 union {int len; lua_Number dummy} u; 來取代。)

Lua API 不允許建立沒有環境資料表的使用者資料,而我找到的所有存在性檢查其他解法都比較慢。

以下是 getter 方法,預期在 upvalue index 2 找到物件的方法資料表(我在所有方法中都使用 upvalue 1 作為 meta 資料表)

/* getter and setter, allowing overriding in 5.1. 5.0 doesn't have environment tables */
static int lvec_meta_index (lua_State *L) {
  int len = 0;
  lvec_t *u = getveclen(L, 1, &len); 
首先,我們會檢查金鑰是否為合理的數字索引,如果不是,則傳回 nil。(我的看法是,getter 不應該引發錯誤,它們應僅傳回 nil。這與一般的資料表語意一致,同時也讓撰寫不會產生隨機錯誤的程式碼更容易。)
  if (lua_type(L, 2) == LUA_TNUMBER) {
    int idx = checkkey(L, len);
    if (idx) {
      lua_pushnumber(L, u->d[idx-1]);
      return 1;
    }
    else
      return 0;
  }
通常,我將在此時檢查 getter 方法。我稍後會找到此類程式碼的良好範例,但在這個特殊的程式設計工作中,檢查有些許特殊性
  if (lua_type(L, 2) == LUA_TSTRING) {
    size_t slen;
    const char *key = lua_tolstring(L, 2, &slen);
    if (slen == 1) {
      int idx = key[0] - 'x';
      if (len <= 3 && idx >= 0 && idx < len) {
        lua_pushnumber(L, u->d[idx]);
        return 1;
      }
    }
  }
現在,如果它不是內建屬性,我們會檢查是否有建立環境資料表,如果有的話,就搜尋其中的金鑰。我們尚未檢查執行個體方法,這允許為特定物件覆寫執行個體方法。事實上,如果我在這個特定程式碼中沒有鎖定與 5.0 半相容性,我將會使用不同的解法,讓方法資料表成為環境資料表的 __index meta 方法,一旦建立環境資料表,表示最後一次的搜尋只能進行一次,同時也能讓物件在 Lua 中建立子類別。
#ifdef VERSION51
  if (u->has_env) {
    lua_getfenv(L, 1);
    lua_pushvalue(L, 2);
    lua_gettable(L, -2);
    if (!lua_isnil(L, -1))
      return 1;
    lua_pop(L, 2);
  }
#endif
最後,在被作為 upvalue 2 附加至 __index 函式(以加速搜尋速度)的執行個體方法資料表中,搜尋金鑰。如果在此資料表中找不到,我們只傳回 nil
  lua_gettable(L, lua_upvalueindex(2));
  return 1;
}

__newindex 方法略有不同的邏輯。同前面,它首先檢查數字鍵和短字串鍵,但此情況中,壞的數字鍵或壞的數字都會拋出錯誤訊息。(呼叫 luaL_checknumber 不正確,我是偷懶:我之後會修正它。它會產生難以解讀的錯誤訊息。)如果失敗,它會在必要時建立一個環境表格,再將鍵值對插入到新的表格。(如前述,我通常在建立新的環境時,會將方法表格附加一個 __index meta。)

static int lvec_meta_newindex (lua_State *L) {
  int len = 0;
  lvec_t *u = getveclen(L, 1, &len);
  if (lua_type(L, 2) == LUA_TNUMBER) {
    int idx = checkkey(L, len);
    if (idx)
      u->d[idx-1] = luaL_checknumber(L, 3);
    else
      luaL_error(L, "Vector index not integer or out of range");
    return 0;
  }
  if (lua_type(L, 2) == LUA_TSTRING) {
    size_t slen;
    const char *key = lua_tolstring(L, 2, &slen);
    if (slen == 1) {
      int idx = key[0] - 'x';
      if (len <= 3 && idx >= 0 && idx < len) {
        u->d[idx] = luaL_checknumber(L, 3);
        return 0;
      }
    }
  }
#ifdef VERSION51
  if (!u->has_env) {
    lua_newtable(L); 
    lua_pushvalue(L, -1);
    lua_setfenv(L, 1);
    u->has_env = 1;
  }
  else
    lua_getfenv(L, 1);
  lua_replace(L, 1);
  lua_settable(L, 1);
#else
  else
    luaL_error(L, "Vector index not integer or out of range");
#endif
  return 0;
}

討論

Rici,這是一個好/實用的範例。老實說,範例輸出連結壞掉了。我花了些時間才弄懂這個頁面的主要目的。雖然最上面有寫「環境表格用於儲存未知鍵的『依需要』」,但我對什麼樣的背景以及 5.0 v.s. 5.1 的因素如何造成影響還很茫然。但我現在懂了(雖然我尚未完全理解),這就是一種實作 __index 和 __newindex metamethods 的技術,它可以用來處理使用者資料,它可以很好地處理元表格,同時很明顯的依需要/延遲建構。它受到了 5.1 如何讓 C 函式使用個別環境表格的影響。5.0/5.1 相容性的聲明目標可以寫在前面。過度使用括號()會降低可讀性。這可以放到 Lua Fu 中的 LuaDirectory。我認為可能有個地方可用於所謂的設計模式。--DavidManura

我修復了連結。你說得對,它絕對需要被編輯。我總是會在初稿中用太多括號,可能是小時候學 LISP 的影響 :) -- RiciLake

最近變更 · 偏好設定
編輯 · 歷程
最後編輯於 2006 年 9 月 26 日 上午 9:50 GMT (差異)