4

I got several objects in several tables. Multiple functions alter and handover the objects to other functions.

Lets say my table is this:

objectTable = {obj1, obj2, obj3}
otherobjTable = {objA, objB, objC, objD}

And let's say these are initialized at the main.lua.

Now, when tracing obj1, it's altered by a function, which alters it and gives an reference to another function, which again, alters it. One step could look like:

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

The problem now is, after received_Object = nil, the reference points nil but the object still exists. How can I get it deleted for sure?

rsc
  • 10,348
  • 5
  • 39
  • 36
jawo
  • 856
  • 1
  • 8
  • 24

1 Answers1

8

In Lua, certain types (like tables) are always passed by reference, while other types (like numbers) are always passed by value.

Moreover, Lua is a language where memory is managed by garbage collector. Garbage collector deletes an object (a table, for instance), when there are no more references (let's call them anchors) to it.

Now this code:

local t = {}
local t1 = {t}
someFunc(t)

creates three anchors to that table. When someFunc passes another that table as an argument to another function, a fourth anchor will be created (in form of that function's local variable/argument).

In order for a garbage collector to sweep the first table, all those references must be gone (either by assigning nil or by going out of scope).

It's important to understand, that when you assign nil to the local t, it does not mean that the table will be deleted. Even less so that all references to that table will be invalidated. It means that you're just freeing this one anchor, which is just one of four at that point.

Possible solution

One possible solution would be to pass around the table holding your object along with the index/key, at which the object is stored:

function func(myTable, myKey)
...
end

Now if in this function you do this:

myTable[myKey] = nil

(and no other anchors get created), the object under the key will have no more references pointing to it and will be marked for sweeping by the garbage collector the next time round. Of course, callAnotherFunction would have to be modified in the same way as well:

callAnotherFunction(myTable, myKey)
...
end

If you perform many operations on that objects within those functions, you may cache it into a local variable to avoid several table lookups. This is ok, as when the function finishes, the anchor will be cleared along with the local variable:

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.

Another solution

Since you can't afford to change your code as much as suggested above, you could implement the following logic:

Create a metatable for tables that hold your objects:

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)

Now when you insert an object into that table:

container1.obj1 = obj1
container2.obj1 = obj1

each time the __newindex metamethod will invoke obj1.storeTableReference with appropriate references. This function will store those references in (for instance) an internal table.

The only thing that's left to implement is a method for your object that frees those references:

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

Now this solution is a bit clumsy, as there are a few things you need to be cautious about:

  • __newindex only get's fired when the key is first created. So container1.obj = obj1 and container1.obj = obj2 would only trigger __newindex on the first assignment. The solution would be to first set the obj key to nil and then to obj2.
  • when you set obj to nil in that table manually (or to another object), you need to make sure that the reference the object stores gets cleared as well.
W.B.
  • 5,445
  • 19
  • 29
  • So there's no way to clear the memory-location? Due by tostring(object) I get the memorylocation. I hacked another solution, by iterating through all tables the object could be in. This works but is, as I thing, the most painfull and stupiest way. – jawo Oct 01 '14 at 10:31
  • I keep your tactic in mind for later projects, but if possible, this time I would prefere if there's a solution in which I dont have to scope through 10.000 lines of code. Due there are less then 100 objects in a maximum of 3 tables, iteration will not take too long. – jawo Oct 01 '14 at 10:35
  • 1
    @Sempie It's a bit hard to suggest anything without knowing anything about your code. Another solution would be to let your objects (are they tables or C userdata?) store references to all tables/keys that reference them. Then implement a `delete` method in your object that iterates through those tables and sets appropriate keys to `nil`. This could be semi automated on table inserts by having `__newindex` metamethod in those tables store those references upon inserts. Perhaps `weak` tables could also be a solution - it's hard to tell without knowing your project. – W.B. Oct 01 '14 at 10:40
  • That sounds great. In my case this probably will be a lot easier to implement, as changing every parameter. If you would add that Idea to your answer, I'll mark it as the solution. Thank you. – jawo Oct 01 '14 at 10:44
  • Nice explanation but you used the catch phrases "pass by reference" and "pass by value". Not to cause confusion but that's not how computer science and languages like C++ use the terms. There is long discussion [here](http://stackoverflow.com/a/40523/2226988). – Tom Blodget Oct 01 '14 at 23:20