2012-10-07 38 views
0

我使用Lua的C API來擴展Lua。在我的模塊中,我想使用luaL_ref填充表格,並使用luaL_unref刪除字段。我也希望能夠迭代這張表,希望使用lua_next如何遍歷用luaL_ref和luaL_unref修改的表?

因爲luaL_unref,遍歷整個表是一個問題。在Lua中,通過分配nil(因爲未初始化的表字段計算爲nil)來「刪除」表字段是很常見的。 next功能足夠智能,可跳過nil。我預計luaL_unrefnil分配給未引用的表字段,但似乎分配了一個整數。這個整數的值似乎沒有記錄。

考慮下面的代碼:

/* tableDump prints a table: */ 
/* key: value, key: value, ... */ 

lua_newtable(L); 

lua_pushboolean(L, 0); 
int ref1 = luaL_ref(L, -2); 

lua_pushinteger(L, 7); 
int ref2 = luaL_ref(L, -2); 

lua_pushstring(L, "test"); 
int ref3 = luaL_ref(L, -2); 

tableDump(L, -1); 

luaL_unref(L, -1, ref1); 
tableDump(L, -1); 

luaL_unref(L, -1, ref3); 
tableDump(L, -1); 

luaL_unref(L, -1, ref2); 
    tableDump(L, -1); 

printf("done.\n"); 

輸出:

1: false, 2: 7, 3: `test', 
3: `test', 2: 7, 0: 1, 
3: 1, 2: 7, 0: 3, 
3: 1, 2: 3, 0: 2, 
done. 

這是怎麼回事?我怎麼能解決這個問題?是否有一些技巧迭代引用並忽略未引用?我必須停止使用luaL_refluaL_unref嗎?


編輯

首先,感謝您的答覆!

也許我問過錯誤的問題。

請允許我更具體些。我有一個客戶端userdata需要管理許多訂閱用戶數據庫。訂閱由客戶的訂閱方法創建。訂閱由客戶的取消訂閱方法刪除。訂閱userdatas基本上是一個實現細節,所以它們不在客戶端API中公開。相反,客戶端API使用訂閱引用,因此使用luaL_ref來填充訂閱表。

ref = client:sub(channel, func) 
cleint:unsub(ref) 

這裏是捕捉。我希望客戶端自動退訂__gc上的所有剩餘訂閱(否則用戶將得到段錯誤)。所以看來我需要迭代訂閱。我真的濫用API嗎?有一個更好的方法嗎?

+0

我使用的是Lua 5.1。 – tprk77

+0

停止濫用API。 'luaL_ref'和'luaL_unref'的目的是*不*填充表格。這是創建一個簡單的參考系統,可以通過引用存儲值並稍後再次獲取它們。你不應該在表格上迭代;你應該使用返回的引用來訪問表條目。 –

回答

2

luaL_refluaL_unref函數在lauxlib.c中定義。他們通過跟蹤他們存儲在他們正在操作的表中的免費參考列表來工作。這些功能相對較短,所以我將把它們包括在這裏。

LUALIB_API int luaL_ref (lua_State *L, int t) { 
    int ref; 
    t = abs_index(L, t); 
    if (lua_isnil(L, -1)) { 
    lua_pop(L, 1); /* remove from stack */ 
    return LUA_REFNIL; /* 'nil' has a unique fixed reference */ 
    } 
    lua_rawgeti(L, t, FREELIST_REF); /* get first free element */ 
    ref = (int)lua_tointeger(L, -1); /* ref = t[FREELIST_REF] */ 
    lua_pop(L, 1); /* remove it from stack */ 
    if (ref != 0) { /* any free element? */ 
    lua_rawgeti(L, t, ref); /* remove it from list */ 
    lua_rawseti(L, t, FREELIST_REF); /* (t[FREELIST_REF] = t[ref]) */ 
    } 
    else { /* no free elements */ 
    ref = (int)lua_objlen(L, t); 
    ref++; /* create new reference */ 
    } 
    lua_rawseti(L, t, ref); 
    return ref; 
} 

LUALIB_API void luaL_unref (lua_State *L, int t, int ref) { 
    if (ref >= 0) { 
    t = abs_index(L, t); 
    lua_rawgeti(L, t, FREELIST_REF); 
    lua_rawseti(L, t, ref); /* t[ref] = t[FREELIST_REF] */ 
    lua_pushinteger(L, ref); 
    lua_rawseti(L, t, FREELIST_REF); /* t[FREELIST_REF] = ref */ 
    } 
} 

這些功能非常聰明,因爲它們不需要額外的存儲空間(除了它們正在運行的表格之外)。但是,將自由引用列表存儲在與引用值相同的表中,例如在需要迭代引用的值時,偶爾會不受歡迎。

我寫了luaX_refluaX_unref來解決這個問題。他們的工作幾乎與luaL_refluaL_unref相同,只是他們將免費參考列表存儲在單獨的表中,l對於列表。 (我的項目,我在一個單獨的源文件把這些並在必要時已包括他們,我不建議修改lauxlib.c。)

static int abs_index(lua_State * L, int i){ 
    return i > 0 || i <= LUA_REGISTRYINDEX ? i : lua_gettop(L) + i + 1; 
} 

LUALIB_API int luaX_ref(lua_State *L, int t, int l){ 
    int ref; 
    t = abs_index(L, t); 
    l = abs_index(L, l); 
    if(lua_isnil(L, -1)){ 
    lua_pop(L, 1); /* remove from stack */ 
    return LUA_REFNIL; /* 'nil' has a unique fixed reference */ 
    } 
    lua_rawgeti(L, l, FREELIST_REF); /* get first free element */ 
    ref = (int) lua_tointeger(L, -1); /* ref = l[FREELIST_REF] */ 
    lua_pop(L, 1); /* remove it from stack */ 
    if(ref != 0){ /* any free element? */ 
    lua_rawgeti(L, l, ref); /* remove it from list */ 
    lua_rawseti(L, l, FREELIST_REF); /* (l[FREELIST_REF] = l[ref]) */ 
    }else{ /* no free elements */ 
    ref = (int)lua_objlen(L, l); 
    ref++; /* create new reference */ 
    } 
    lua_pushboolean(L, 1); 
    lua_rawseti(L, l, ref); /* l[ref] = true */ 
    lua_rawseti(L, t, ref); /* t[ref] = value */ 
    return ref; 
} 

LUALIB_API void luaX_unref(lua_State *L, int t, int l, int ref){ 
    if(ref >= 0){ 
    t = abs_index(L, t); 
    l = abs_index(L, l); 
    lua_rawgeti(L, l, FREELIST_REF); 
    lua_rawseti(L, l, ref); /* l[ref] = l[FREELIST_REF] */ 
    lua_pushinteger(L, ref); 
    lua_rawseti(L, l, FREELIST_REF); /* l[FREELIST_REF] = ref */ 
    lua_pushnil(L); 
    lua_rawseti(L, t, ref); /* t[ref] = nil */ 
    } 
} 

現在看到的用法:

lua_newtable(L); /* 1 */ 
lua_newtable(L); /* 2 */ 

lua_pushboolean(L, 0); 
int ref1 = luaX_ref(L, 1, 2); 

lua_pushinteger(L, 7); 
int ref2 = luaX_ref(L, 1, 2); 

lua_pushstring(L, "test"); 
int ref3 = luaX_ref(L, 1, 2); 

tableDump(L, 1); 
tableDump(L, 2); 

luaX_unref(L, 1, 2, ref1); 
tableDump(L, 1); 
tableDump(L, 2); 

luaX_unref(L, 1, 2, ref3); 
tableDump(L, 1); 
tableDump(L, 2); 

luaX_unref(L, 1, 2, ref2); 
tableDump(L, 1); 
tableDump(L, 2); 

printf("done.\n"); 

輸出:

1: false, 2: 7, 3: `test', 
1: true, 2: true, 3: true, 
2: 7, 3: `test', 
3: true, 2: true, 0: 1, 
2: 7, 
3: 1, 2: true, 0: 3, 

3: 1, 2: 3, 0: 2, 
done. 
2

爲了「確保密鑰的唯一性返回」luaL_ref必須保留已使用的密鑰列表,然後luaL_unref已刪除的密鑰。看起來這個清單開始於t[0]並且一直持續到指數鏈導致nil。該列表與活動引用保持在同一個表中。

如果您想繼續像Nicol觀察到的那樣「濫用API」,並且依賴於實現定義的行爲,那麼您可以在循環訪問表時查看該鏈表是否爲已刪除的引用。或者爲了避免對實現定義的行爲的依賴,並且性能更高,您可以保留一個單獨的已刪除引用表並在迭代表時忽略它們,儘管您需要忽略列表頭t[0]

如果你真的需要迭代引用,你可能會更好地使用不同的機制。您可以簡單地將所有參考/值對放入單獨的表中,並在刪除參考時將單獨表中的值設置爲nil。然後你可以簡單地迭代單獨的表。