メタメソッド
関数 | 説明 |
---|---|
__追加 | 演算子+ |
__サブ | オペレーター- |
__私は持っています | 演算子* |
__ div | オペレーター/ |
__モッド | オペレーター% |
__unm | 演算子-(否定) |
__concat | オペレーター: |
__eq | 演算子== |
__lt | 演算子< |
__the | 演算子<= |
__コール | 関数が呼び出されたとき |
__tostring | 文字列に変換 |
__index | インデックスを呼び出す |
__newindex | インデックスを割り当てる |
これらの演算子は使用法が似ているため、個別に説明することはしません。次に、__ call、__ tostring、__ index、および__newindexの4つのメタメソッドについて説明します。
__コール
__callを使用すると、テーブルを関数として使用できます。
local mt = {}
--__call的第一参数是表自己
mt.__call = function(mytable,...)
--输出所有参数
for _,v in ipairs{...} do
print(v)
end
end
t = {}
setmetatable(t,mt)
--将t当作一个函数调用
t(1,2,3)
結果:
1
2
3
__tostring
__tostringは、テーブルを文字列に変換する動作を変更できます
local mt = {}
--参数是表自己
mt.__tostring = function(t)
local s = "{"
for i,v in ipairs(t) do
if i > 1 then
s = s..", "
end
s = s..v
end
s = s .."}"
return s
end
t = {1,2,3}
--直接输出t
print(t)
--将t的元表设为mt
setmetatable(t,mt)
--输出t
print(t)
結果:
table: 0x14e2050
{1, 2, 3}
__index
テーブルの存在しないインデックスを呼び出す場合、メタテーブルの__indexメタメソッドが使用されます。以前のメタメソッドとは異なり、__ indexは関数またはテーブルにすることができます。
関数として:
テーブルとインデックスをパラメータとして__indexメタメソッドに渡し、戻り値を返します
local mt = {}
--第一个参数是表自己,第二个参数是调用的索引
mt.__index = function(t,key)
return "it is "..key
end
t = {1,2,3}
--输出未定义的key索引,输出为nil
print(t.key)
setmetatable(t,mt)
--设置元表后输出未定义的key索引,调用元表的__index函数,返回"it is key"输出
print(t.key)
結果:
nil
it is key
テーブルとして:
__ indexメタメソッドテーブルを見つけます。インデックスがある場合は、インデックスに対応する値を返します。それ以外の場合は、nilを返します。
local mt = {}
mt.__index = {key = "it is key"}
t = {1,2,3}
--输出未定义的key索引,输出为nil
print(t.key)
setmetatable(t,mt)
--输出表中未定义,但元表的__index中定义的key索引时,输出__index中的key索引值"it is key"
print(t.key)
--输出表中未定义,但元表的__index中也未定义的值时,输出为nil
print(t.key2)
結果:
nil
it is key
nil
__newindex
テーブル内の存在しないインデックスに値を割り当てると、メタテーブル内の__newindexメタメソッドが関数
として呼び出され
ます.__ newindexが関数の場合、割り当てステートメント内のテーブル、インデックス、および割り当て値は次のようになりますパラメータと呼ばれます。テーブルに変更はありません
local mt = {}
--第一个参数时表自己,第二个参数是索引,第三个参数是赋的值
mt.__newindex = function(t,index,value)
print("index is "..index)
print("value is "..value)
end
t = {key = "it is key"}
setmetatable(t,mt)
--输出表中已有索引key的值
print(t.key)
--为表中不存在的newKey索引赋值,调用了元表的__newIndex元方法,输出了参数信息
t.newKey = 10
--表中的newKey索引值还是空,上面看着是一个赋值操作,其实只是调用了__newIndex元方法,并没有对t中的元素进行改动
print(t.newKey)
結果:
it is key
index is newKey
value is 10
nil
テーブル
__newindexはテーブルであるため、tに存在しないインデックスに値を割り当てると、元のテーブルを変更せずに、__ newindexが指すテーブルにインデックスと値が割り当てられます。
local mt = {}
--将__newindex元方法设置为一个空表newTable
local newTable = {}
mt.__newindex = newTable
t = {}
setmetatable(t,mt)
print(t.newKey,newTable.newKey)
--对t中不存在的索引进行负值时,由于t的元表中的__newindex元方法指向了一个表,所以并没有对t中的索引进行赋值操作将,而是将__newindex所指向的newTable的newKey索引赋值为了"it is newKey"
t.newKey = "it is newKey"
print(t.newKey,newTable.newKey)
結果:
nil nil
nil it is newKey
rawgetとrawset
テーブルの値を直接変更または取得する場合は、rawgetメソッドとrawsetメソッドが必要になることがあります。
Rawgetを使用すると、メタテーブルの__indexメタメソッドを経由せずに、テーブル内のインデックスの実際の値を直接取得できます。
local mt = {}
mt.__index = {key = "it is key"}
t = {}
setmetatable(t,mt)
print(t.key)
--通过rawget直接获取t中的key索引
print(rawget(t,"key"))
結果:
it is key
nil
rawsetを使用すると、メタテーブルの__newindexメタメソッドを渡すことなく、テーブル内のインデックスに値を直接割り当てることができます。
local mt = {}
local newTable = {}
mt.__newindex = newTable
t = {}
setmetatable(t,mt)
print(t.newKey,newTable.newKey)
--通过rawset直接向t的newKey索引赋值
rawset(t,"newKey","it is newKey")
print(t.newKey,newTable.newKey)
結果:
nil nil
it is newKey nil
メタテーブルのシナリオを使用する
テーブルとしてメタテーブル
テーブルにメタテーブルを設定することで、オブジェクト指向プログラミングをLuaに実装できます。
ユーザーデータとしてのメタテーブル
Luaのcの構造体へのオブジェクト指向アクセスは、ユーザーデータとメタテーブルを介して実現できます。
弱い参照テーブル
Pythonなどのスクリプト言語と同様に、Luaも自動メモリ管理(ガベージコレクション)を使用します。プログラムは、オブジェクトを削除せずにオブジェクトを作成するだけで済みます。ガベージコレクションメカニズムを使用することで、Luaは期限切れのオブジェクトを自動的に削除します。ガベージコレクションメカニズムにより、プログラマーは、C言語で頻繁に発生するメモリリークや無効なポインター参照などの低レベルのバグから解放されます。
Pythonのガベージコレクションメカニズムは参照カウントアルゴリズムを使用していることがわかっています。オブジェクトを指すすべての名前が無効な場合(有効期間を超えるか、プログラマーが明示的にdelを超える場合)、オブジェクトが占有していたメモリが再利用されます。ただし、循環参照の場合は特殊なケースであり、ガベージコレクターは通常それを認識できません。これにより、循環参照を持つオブジェクトの参照カウンターがゼロになることはなく、リサイクルされる可能性はありません。
Pythonで循環参照を使用する例:
class main1:
def __init__(self):
print('The main1 constructor is calling...')
def __del__(self):
print('The main1 destructor is calling....')
class main2:
def __init__(self, m3, m1):
self.m1 = m1
self.m3 = m3
print('The main2 constructor is calling...')
def __del__(self):
print('The main2 destructor is calling....')
class main3:
def __init__(self):
self.m1 = main1()
self.m2 = main2(self, self.m1)
print('The main3 constructor is calling...')
def __del__(self):
print('The main3 destructor is calling....')
# test
main3()
出力は次のとおりです。
The main1 constructor is calling...
The main2 constructor is calling...
The main3 constructor is calling...
デストラクタ(__del__関数)が呼び出されておらず、循環参照によってメモリリークが発生していることがわかります。
ガベージコレクターは、ユーザーがガベージと見なすものではなく、ガベージと見なされるもののみを収集できます。たとえば、グローバル変数に格納されているオブジェクトは、プログラムがそれらを使用しなくなった場合でも、ユーザーがこれらのオブジェクトを解放できるようにnilに割り当てない限り、Luaにとってゴミではありません。ただし、参照をクリアするだけでは不十分な場合もあります。たとえば、オブジェクトが配列に配置されている場合、オブジェクトをリサイクルすることはできません。これは、現在他の場所で使用されていなくても、ユーザーがいない限り、配列が参照しているためです。 Luaへのこの参照は、このオブジェクトのリサイクルを妨げるべきではないことを伝えます。そうしないと、Luaはそれを知りません。
テーブルにはキーと値があり、どちらにも任意のタイプのオブジェクトを含めることができます。通常、ガベージコレクターは、アクセス可能なテーブルのキーまたは値であるオブジェクトを再利用しません。言い換えれば、これらのキーと値は強力な参照であり、参照されたオブジェクトのリサイクルを防ぎます。弱参照テーブルでは、キーと値をリサイクルできます。
弱い参照テーブル(弱いテーブル)は、参照がオブジェクトの回復を妨げてはならないことをLuaに伝えるためにユーザーによって使用されます。いわゆる弱参照は、ガベージコレクターによって無視されるオブジェクト参照です。オブジェクトの参照が弱参照である場合、オブジェクトもリサイクルされ、弱参照自体を何らかの形で削除できます。
弱参照テーブルには次の3つのタイプがあります。
1.弱い参照キーを
持つテーブル; 2。弱い参照値を
持つテーブル; 3。弱い参照キーと値が同時にあるテーブル;
テーブルの弱参照タイプは、メタテーブルの__modeフィールドによって決定されます。このフィールドの値は文字列である必要があります。
「k」が含まれている場合、このテーブルのキーは弱く参照されます。
「v」が含まれている場合、このテーブルの値は弱く参照されます。
テーブルを弱く参照する例を次に示します。これは、collectgarbage関数を使用してガベージコレクションを強制する方法です。
a = {1,4, name='cq'}
setmetatable(a, {__mode='k'})
key = {}
a[key] = 'key1'
key = {}
a[key] = 'key2'
print("before GC")
for k, v in pairs(a) do
print(k, '\t', v)
end
collectgarbage()
print("\nafter GC")
for k, v in pairs(a) do
print(k, '\t', v)
end
出力:
before GC
1 1
2 4
table: 0x167ba70 key1
name cq
table: 0x167bac0 key2
after GC
1 1
2 4
name cq
table: 0x167bac0 key2
この例では、2番目の文の割り当てkey = {}が最初のキーを上書きします。コレクターが実行されると、最初のキーを参照する場所がないため、最初のキーがリサイクルされ、テーブル対応するエントリも削除されます。 。2番目のキーについては、可変キーが引き続き参照しているため、リサイクルされていません。
弱参照テーブル内のオブジェクトのみをリサイクルでき、数値、文字列、ブール値などの「値」はリサイクルできないことに注意してください。
メモ化関数は、時間にスペースを使用する方法です。たとえば、通常のサーバーが要求を受信した場合、コード文字列でloadstringを呼び出してから、コンパイルされた関数を呼び出す必要があります。ただし、loadstringは高価な関数です。サーバーに送信されるコマンドの中には、「close()」などの頻度が高いものがあります。このようなコマンドを受信するたびにloadstringが呼び出される場合は、サーバーに補助を使用させることをお勧めします。テーブルには、loadstringへのすべての呼び出しの結果が記録されます。
メモ機能の例:
local results = {}
setmetatable(results, {__mode='v'})
function mem_loadstring(s)
local res = results[s]
if res == nil then
res=assert(loadstring(s))
results[s]=res
end
return res
end
local a = mem_loadstring("print 'hello'")
local b = mem_loadstring("print 'world'")
a = nil
collectgarbage()
for k,v in pairs(results) do
print(k, '\t', v)
end
この例では、テーブルの結果は、サーバーが受信したすべてのコマンドとそのコンパイル結果を徐々に蓄積します。一定期間が経過すると、大量のメモリが消費されます。弱い参照テーブルでこの問題を解決できます。結果テーブルの参照値が弱い場合、すべてのガベージコレクションは、実行中に未使用のコンパイル結果をすべて削除します。
で記事Luaのメタテーブル、どのようにデフォルト値を使用してテーブルを実装する言及されました。各テーブルにデフォルト値を設定し、これらのデフォルト値を保持したくない場合は、次の例のように、テーブルへの弱参照を使用することもできます。
local defaults = {}
setmetatable(defaults, {__mode='k'})
local mt = {__index=function(t) return defaults[t] end}
function setDefault(t, d)
defaults[t] = d
setmetatable(t, mt)
end
local a = {}
local b = {}
setDefault(a, "hello")
setDefault(b, "world")
print(a.key1)
print(b.key2)
b = nil
collectgarbage()
for k,v in pairs(defaults) do
print(k,'\t',v)
end
弱参照の適用:Luaでのメモリリーク検出
Luaのメモリリークを検出するための2つのレベルもあります。
レベル1:luaレイヤー内のluaオブジェクトのリークと、luaレイヤーによって引き起こされたcオブジェクトのリークを同時に検出します。
レベル2:luaレイヤー内のluaオブジェクトのリークのみを検出します。
レベル1を満たすメモリリークの検出は比較的少し複雑ですが、原則はまだ比較的単純です。つまり、2つの時点でlua状態をスキャンしてスナップショットを取得し、2つのスナップショットを比較します。後者は次のカードの時に割り当てられたメモリが前のカードのメモリよりも多いです。これらの2つの時点が機能的に論理的に一貫している場合(たとえば、前の時点がコピーに入る前であり、次の時点がコピーを出た後である場合)、追加のメモリ参照はリークされたメモリです。
ここでは、レベル2の実装に焦点を当てます。luaレイヤー内のluaオブジェクトのリークのみを検出することも非常に重要です。コードスケールが大きく、鋭い視力と厳密な脳分析によってメモリリークを検出することは非常に難しいため、自分自身を助けるための特定のツールが必要です。ここでは、luaが提供する「弱参照」を使用できます。「弱い参照」は一種の参照です。オブジェクトへの参照は、lua gcメカニズムによるオブジェクトのガベージコレクションに影響を与えません。つまり、オブジェクトに弱い参照しかない場合、オブジェクトはgcによって再利用されます。 -要するに、gcは弱い参照を無視します。
したがって、キー値とコンテンツ値への参照が「弱参照」(つまり、メタテーブルの__mode要素属性値が「kv」)であるグローバルメモリリーク監視弱参照テーブルを確立できます。気になるオブジェクトテーブルに配置します。次に、このオブジェクトがリサイクルされるべきだと思われる瞬間を待ち、テーブルにこのオブジェクトがまだあるかどうかを確認します。存在しない場合は、オブジェクトが正しくリサイクルされたことを意味します。存在する場合は、オブジェクトが正しくリサイクルされていないことを意味します。つまり、対象が漏れました。