弱参照はLua C APIで使用できますが、Lua言語自体には標準的なサポートがありません。このノートでは、Luaにおける弱参照のインターフェースを提案し、その実装について説明し、テーブルオブジェクトの安全なデストラクタイベントやオブジェクトキャッシングなど、いくつかの実践的な使用例を示します。
-- creation ref = weakref(obj) -- dereference obj = ref()つまり、新しいグローバル関数「weakref」を使用して、オブジェクトへの弱参照を作成します。弱参照は、関数呼び出し演算子を使用して逆参照できます。逆参照の結果がnilの場合は、オブジェクトがガベージコレクションされたことを意味します。nilにはこの特別な意味があるため、nilオブジェクト自体の弱参照は許可されません。
私たちの`weakref`関数は`lua_ref()`を呼び出して、結果の参照IDを保持するオブジェクトを返す必要があります。参照オブジェクトの関数呼び出しタグメソッドで実装された逆参照は、単に`lua_getref()`を呼び出します。最後に、参照オブジェクト自体が収集されたときに参照を解放する必要があるため、ガベージコレクション(gc)タグメソッドを使用して`lua_unref()`を呼び出します。
ユーザーデータ型は、gcイベントを提供する唯一の型であるため、参照オブジェクトに最適な選択です。さらに、必要なのは整数1つだけなので、状態をユーザーデータポインタ自体に格納することで、動的なメモリ割り当てを排除できます。
この実装のソースコードは、公式Lua 4.0配布版へのパッチとして提供されており、こちらで入手できます。パッチユーティリティを使用して次のように適用します。
cd <lua distrubution directory> patch -p1 < weakrefs.patchこのパッチには、拡張機能の簡単な例を示すテストディレクトリ「weakref.lua」への新しい追加が含まれています。
この実装をLuaの「baselib」標準ライブラリに追加することを提案します。その理由は、弱参照は一般的に有用であり、実装はシンプルでC APIによって既にサポートされていること、そして新しいLua関数は1つだけなので、その目的のために別のライブラリを作成するのは過剰であるためです。
しかし、ファイルハンドル、グラフィックバッファなど、自動的に解放されないシステムリソースをオブジェクトが所有する場合があります。やや面倒な解決策は、gcイベントを持つユーザーデータ型を使用してCからそのようなオブジェクトを実装することです。弱参照はこれに対するエレガントな代替手段を提供し、Luaの快適さからテーブルオブジェクトに対して安全なガベージコレクションイベントを可能にします。
この実装では、弱参照/デストラクタ関数のペアを含むテーブルを使用します。参照のオブジェクトが収集されると、対応するデストラクタ関数が呼び出されます。これらのデストラクタは、破壊されるオブジェクトにアクセスできないため安全です。デストラクタに必要な情報(リソースハンドルなど)は、オブジェクトとは別にアクセス可能である必要があります。これは、ファーストクラス関数オブジェクトのおかげで、Luaではかなり簡単な作業です。
テーブルを管理するには、デストラクタ関数をオブジェクトにバインドする関数と、収集されたオブジェクトを確認する関数からなる小さなインターフェースが必要です。Luaでの実装を以下に示します。
------------------------------------------ -- Destructor manager local destructor_table = { } function RegisterDestructor(obj, destructor) %destructor_table[weakref(obj)] = destructor end function CheckDestructors() local delete_list = { } for objref, destructor in %destructor_table do if not objref() then destructor() tinsert(delete_list, objref) end end for i = 1, getn(delete_list) do %destructor_table[delete_list[i]] = nil end end`CheckDestructors()`を手動で定期的に呼び出す代わりに、Luaのガベージコレクションサイクルにチェーンするのが自然です。Lua仮想マシンは、サイクルの最後にnil型のgcタグメソッドを呼び出すことでこれをサポートしています。
安全なデストラクタの使用例として、プログラムメッセージをファイルにログ出力するために使用されるオブジェクトを考えてみましょう。オブジェクトがガベージコレクションされると、ログファイルを閉じたいと思います。(この例は、プログラムの終了時にファイルハンドルが閉じられるため単純です。ただし、この方法は他の種類のリソースにも簡単に適用できます。)
------------------------------------------ -- example object using safe destructor function make_logobj(filename) local id = openfile(filename, "w") assert(id) local obj = { file = id, write = function(self, message) write(self.file, message) end, } local destructor = function() closefile(%id) end RegisterDestructor(obj, destructor) return obj end
1つの解決策は、最近アクセスされた上位*n*ページのみをキャッシュすることですが、データサイズを考慮しないため、使用可能なメモリを有効活用できません。改善策としては、最近アクセスされた上位*x*キロバイトの生成済みデータをキャッシュすることです。プログラムの複雑さが増すことに加えて、ここで生じる問題は、*x*の適切な値を見つけることです。これは、ガベージコレクタが直面する問題と似ています。どのくらいの頻度で、そしてどのくらいのメモリ使用量の後、サイクルを実行する必要がありますか?
キャッシングに弱参照を使用することで、プログラムの複雑さを低く抑えながら、メモリ使用量の問題をガベージコレクタに任せることができます。生成されたページオブジェクトを格納する代わりに、キャッシュテーブルにはそれらのオブジェクトへの弱参照が含まれています。ガベージコレクションサイクルが発生すると、現在使用されていないページオブジェクトが収集されます。
ここでは、その「名前」が与えられたページオブジェクトを作成する関数`GeneratePage()`を仮定した実装を示します。`CleanCache()`関数は、収集されたオブジェクトのテーブルエントリを削除するために必要であり、これもLuaのgcサイクルにチェーンする必要があります。
------------------------------------------ -- Page cache local cache_table = { } function GetPage(name) local ref = %cache_table[name] local obj = ref and ref() if not obj then obj = GeneratePage(name) %cache_table[name] = weakref(obj) end return obj end function CleanCache() local delete_list = { } for name, ref in %cache_table do if not ref() then tinsert(delete_list, name) end end for i = 1, getn(delete_list) do %cache_table[delete_list[i]] = nil end end