技术说明 8
非缓存实现相当简单。它在表中使用一个 _parents = {} 成员来创建一个继承链。
-- ********************************************************
-- index tag method AdvProtoIndex(t,f)
--
-- This is a 'simple' version of the multiple inheritance
-- tag method. It does not cache and thus it is quite slow.
-- However, if you think something strange is happening, you
-- can fall back to this version and see if the strangeness
-- goes away.
function AdvProtoIndex (t,f)
if f == '_parents' then -- to avoid loop
if (OldIndex) then
return OldIndex(t,f)
else
return nil;
end
end
local p = t["_parents"];
if (type(p) == 'table') then
local cur_index = 1; -- start at 1
local cur_data;
repeat
cur_data = p[cur_index];
if (type(cur_data) == 'table') then
local result = cur_data[f];
if (result ~= nil) then
return result; -- we found a match
end
else
if (OldIndex) then
return OldIndex(t,f);
else
return nil;
end
end
cur_index = cur_index + 1; -- do next parent
until (cur_data == nil);
return nil; -- we didn't find a match
else
return nil;
end
end
我通常为所有表设置此回退标记方法,这意味着创建继承与创建具有适当成员的表一样简单
a_base = {
a_number = 1
}
b_base = {
b_number = 2
}
ab_class = {
_parents = { a_base, b_base }
}
print(ab_class.a_number); -- yields "1"
print(ab_class.b_number); -- yields "2"
使用上述简单实现,很容易创建严重影响运行时性能的继承链,因为继承的方法调用或实例数据访问可能导致 2n 次查找,其中 n 是整个链中继承的基类的数量。
提供了一个性能优化的实现,其功能基本相同,但速度大大提高。
----------------------------------------------------------
-- AdvProtoIndexWithCache
--
-- This inheritance tag method handles multiple inheritance via a
-- "_parent = {}" table. It caches information in the top-level table
-- to make lookups fast.
--
-- Example:
--
-- This tag method is applied to all tables, so all you have to do to
-- get a magic inheritance table is to do this:
--
-- BaseObj1 = { a_value = "a" }
-- BaseObj2 = { b_value = "b" }
-- MyClass = { _parents = { BaseObj2, BaseObj1 } }
-- MyInstance = { _parents = { MyClass }
--
function setupMultipleInheritenceForTag(a_tag)
-- I like to setup my tag methods within a function because
-- then stuff like these private declarations can be
-- referenced with upvalues and disappear. :)
local NIL_OBJECT = { magic_nil_object = 1}
local SLOT_REF_TAG = newtag()
local OldIndex = gettagmethod(tag({}),"index")
local debug_mode = nil
AdvProtoIndexWithCache = function (t,f, instance, depth)
if (f == '_parents') or (f == '_slotcache') then -- to avoid loop
if (%OldIndex) then
return %OldIndex(t,f)
else
return nil;
end
end
if instance == nil then
-- we are the instance!
instance = t
end
if depth == nil then
depth = 0
end
-- get out the parent table
local p = rawgettable(t,"_parents")
local cache = rawgettable(instance,"_slotcache");
if cache then
local item = rawgettable(cache,f)
if item then
if item == %NIL_OBJECT then
return nil
elseif tag(item) == %SLOT_REF_TAG then
return item.obj[f]
else
return item
end
end
else
-- if we are the instance AND we had a _parents
-- slot, then create the slot cache!
if (instance == t) and (p) then
cache = {}
rawsettable(t,"_slotcache",cache); -- make the slot cache!
end
end
if (type(p) == 'table') then
local cur_index = 1; -- start at 1
local cur_data;
repeat
cur_data = p[cur_index];
if (%debug_mode) then
print("---------", cur_index, depth)
end
if (type(cur_data) == 'table') then
if (%debug_mode) then
printTables(cur_data)
end
-- local result = cur_data[f];
local result = rawgettable(cur_data,f);
if (%debug_mode and (result ~= nil)) then
print("value: ", result)
end
-- if we found the slot in us, then we need
-- to do the caching, because after we return
-- it's not possible to make a SLOT_REF
if ((result ~= nil) and (cache)) then
rawsettable(instance,f,result);
end
if (result == nil) then
result = AdvProtoIndexWithCache(cur_data,f,instance, depth + 1)
end
if (result ~= nil) then
if (%debug_mode and (result ~= nil)) then
print("value: ", result)
end
return result; -- we found a match
end
else
local result
if (%OldIndex) then
result = %OldIndex(t,f);
else
result = nil;
end
if cache then
if (type(result) == "function") then
rawsettable(cache,f,result);
elseif result == nil then
rawsettable(cache,f,%NIL_OBJECT)
else
local slot_ref = { obj = cur_data }
settag(slot_ref,%SLOT_REF_TAG);
rawsettable(cache,f,slot_ref);
end
end
return result; -- we found a match
end
cur_index = cur_index + 1; -- do next parent
until (cur_data == nil);
if cache then
rawsettable(cache,f,%NIL_OBJECT)
end
return nil; -- we didn't find a match
else
return nil;
end
end
settagmethod(a_tag,"index",AdvProtoIndexWithCache); -- Lua 3.1 method
end
在缓存中,函数直接指向,其他项使用称为“SLOT_REF”的项指向。SLOT_REF 是一种特殊类型的表,它是一种按引用而不是按值进行缓存的缓存。它包含对原始值的表和索引的引用,因此,如果原始数据值发生变化,此 SLOT_REF 将正确指向新的数据值。这不会对方法执行,因为已决定方法查找的性能比保留更改基类中方法的能力更重要。
此机制的另一种实现可以通过取消 SLOT_REF 而变得更快,而是使用某种方法使 slotcache 无效(例如维护反向 slotcache 依赖项列表)。每当基类方法或函数发生更改时,反向依赖链的 slotcache 将失效。如果将“_slotcache”更改为在对象的“类”级别而不是像现在这样在“实例”级别发生,这可能会导致仅适度的额外数据保留。
重要的是要认识到,由于缓存版本将信息直接存储在单个扁平表中,因此如果基类方法已在缓存表中,则可能会忽略对它们的更改。实际上,更改基类的方法是一种不频繁的操作。使用此机制时,应完全避免此做法。
由于 Lua(我认为错误地)将 nil 覆盖为 false 和空数据元素的含义,因此无法使用 nil 值覆盖继承的对象成员。这是因为设置 self.a = nil 将导致删除“a”成员,从而导致缺失索引标记方法触发,该方法查阅 _parents 或 _slotcache 表以查找继承的元素“a”。我还没有找到解决此问题的办法。
解决方案的完整代码,包括一些有用的实用程序函数,请参见 此处。