在 Lua 中通过引用删除变量

Sem*_*pie 3 memory lua reference lua-table

我在几个表中得到了几个对象。多个功能更改对象并将其移交给其他功能。

假设我的表是这样的:

objectTable = {obj1, obj2, obj3}
otherobjTable = {objA, objB, objC, objD}
Run Code Online (Sandbox Code Playgroud)

假设这些是在 main.lua 中初始化的。

现在,当跟踪 obj1 时,它被一个函数改变,该函数改变它并提供对另一个函数的引用,另一个函数又改变它。一个步骤可能如下所示:

function()
   if something then func(obj_1)
   elseif something else then func(obj_2)
   elseif something other then func(obj_3)
   //... and so on...
end

function func(received_Object)
  if something then
    table.insert(received_Object, a value)
  end
  callAnotherFunction(received_Object)
end

function callAnotherFunction(received_Object)
  if input == "Delete it" then
    local name = received_Object.name
    received_Object = nil
    return string.format("%s was deleten", name)
  else
    return false
  end
end
Run Code Online (Sandbox Code Playgroud)

现在的问题是,在received_Object = nil 之后,引用指向 nil 但对象仍然存在。我怎样才能确定删除它?

W.B*_*.B. 6

在 Lua 中,某些类型(如表)总是通过引用传递,而其他类型(如数字)总是通过值传递。

此外,Lua 是一种内存由垃圾收集器管理的语言。垃圾收集器删除一个对象(例如一个表),当没有更多的引用(让我们称它们为锚点)时。

现在这个代码:

local t = {}
local t1 = {t}
someFunc(t)
Run Code Online (Sandbox Code Playgroud)

为该表创建三个锚点。什么时候someFunc将另一个该表作为参数传递给另一个函数时,将创建第四个锚点(以该函数的局部变量/参数的形式)。

为了让垃圾收集器清除第一个表,所有这些引用都必须消失(通过分配 nil或超出范围)。

重要的是要了解,当您分配nil给本地t,并不意味着该表将被删除。更不用说对该表的所有引用都将无效。这意味着你只是释放了这个锚点,在这一点上它只是四个锚点之一。

可能的解决方案

一种可能的解决方案是传递包含对象的表以及存储对象的索引/键:

function func(myTable, myKey)
...
end
Run Code Online (Sandbox Code Playgroud)

现在,如果在这个函数中你这样做:

myTable[myKey] = nil
Run Code Online (Sandbox Code Playgroud)

(并且没有其他锚点被创建),键下的对象将不再有指向它的引用,并将被标记为垃圾收集器下一次清除。当然,callAnotherFunction也必须以相同的方式进行修改:

callAnotherFunction(myTable, myKey)
...
end
Run Code Online (Sandbox Code Playgroud)

如果您在这些函数中对该对象执行许多操作,您可以将其缓存到一个局部变量中以避免多次表查找。这没关系,因为当函数完成时,锚点将与局部变量一起被清除:

callAnotherFunction(myTable, myKey)
    local myObj = myTable[myKey]
    ...
    if myCondition then myTable[myKey] = nil end
end  --here myObj is no longer valid, so the anchor is gone.
Run Code Online (Sandbox Code Playgroud)

另一种解决方案

由于您无法像上面建议的那样更改代码,因此您可以实现以下逻辑:

为包含对象的表创建元表:

local mt = {
    __newindex = function(tab, key, val)
        --if necessary and possible, perform a check, if the value is in fact object of your type
        val.storeTableReference(tab, key) --you'll have to implement this in your objects
        rawset(tab, key, val);
    end
}

local container1 = setmetatable({}, mt)
local container2 = setmetatable({}, mt)
Run Code Online (Sandbox Code Playgroud)

现在,当您将对象插入该表时:

container1.obj1 = obj1
container2.obj1 = obj1
Run Code Online (Sandbox Code Playgroud)

每次 __newindex 元方法将调用 obj1.storeTableReference使用适当的引用进行。此函数将这些引用存储在(例如)内部表中。

唯一需要实现的是释放这些引用的对象方法:

myObj:freeReferences = function()
    for k, v in ipairs(self:tableReferences) do --assuming that's where you store the references
        k[v] = nil
    end
    tableReferences = {} --optional, replaces your reference table with an empty one
end
Run Code Online (Sandbox Code Playgroud)

现在这个解决方案有点笨拙,因为您需要注意以下几点:

  • __newindex只有在第一次创建密钥时才会触发。所以 container1.obj = obj1并且container1.obj = obj2只会__newindex在第一次分配时触发。解决方案是首先将obj键设置为 nil 然后设置为obj2
  • 当您obj在该表中手动(或另一个对象)设置为 nil 时,您需要确保对象存储的引用也被清除。