この初版は Lua 5.0 向けに書かれています。後のバージョンにも概ね該当しますが、いくつかの違いがあります。
第4版は Lua 5.3 を対象としており、Amazon やその他の書店で入手できます。
本書をご購入いただくことで、Lua プロジェクトの支援にもなります。


17 – 弱参照テーブル

Lua は自動メモリ管理を行います。プログラムはオブジェクト(テーブル、関数など)を作成するだけで、オブジェクトを削除する関数は存在しません。Lua は *ガベージコレクション* を使用して、ガベージとなったオブジェクトを自動的に削除します。これにより、メモリ管理の負担の大部分と、より重要なこととして、ダングリングポインタやメモリリークなどのメモリ管理に関連するバグの大部分から解放されます。

他のコレクターとは異なり、Lua のガベージコレクターは循環参照の問題がありません。循環データ構造を使用する場合、特別な操作は必要ありません。他のデータと同様に収集されます。それでも、時には賢いコレクターでさえあなたの助けを必要とします。どんなガベージコレクターでも、メモリ管理の心配をすべて忘れさせてくれるわけではありません。

ガベージコレクターは、ガベージであることが確実なものだけを収集できます。何がガベージであるかをコレクターが知ることはできません。典型的な例は、配列とトップへのインデックスを使用して実装されたスタックです。配列の有効な部分はトップまでしか及ばないことをあなたは知っていますが、Lua は知りません。トップをデクリメントするだけで要素をポップすると、配列に残されたオブジェクトは Lua にとってガベージではありません。同様に、グローバル変数に格納されているオブジェクトは、プログラムが二度と使用しなくても、Lua にとってガベージではありません。どちらの場合も、これらの位置に **nil** を代入して、そうでなければ解放されるオブジェクトをロックしないようにするのは、あなた(つまり、あなたのプログラム)次第です。

しかし、単に参照をクリアするだけでは必ずしも十分ではありません。場合によっては、あなたとコレクターとの間の特別な連携が必要になります。典型的な例は、プログラム内の特定の種類のすべてのライブオブジェクト(例:ファイル)のコレクションを保持したい場合です。これは簡単な作業のように思えます。新しいオブジェクトをコレクションに挿入するだけです。しかし、オブジェクトがコレクション内に入ると、それは決して収集されません!他の誰もそれを指していなくても、コレクションは指しています。あなたが Lua にそのことを伝えない限り、Lua はこの参照がオブジェクトの再利用を妨げてはならないことを知ることはできません。

弱参照テーブルは、参照がオブジェクトの再利用を妨げてはならないことを Lua に伝えるために使用するメカニズムです。*弱参照* は、ガベージコレクターによって考慮されないオブジェクトへの参照です。オブジェクトを指すすべての参照が弱参照である場合、オブジェクトは収集され、これらの弱参照は何らかの方法で削除されます。Lua は弱参照を弱参照テーブルとして実装します。*弱参照テーブル* は、すべての参照が弱参照であるテーブルです。つまり、オブジェクトが弱参照テーブル内にのみ保持されている場合、Lua は最終的にそのオブジェクトを収集します。

テーブルにはキーと値があり、どちらもあらゆる種類のオブジェクトを含むことができます。通常の状況下では、ガベージコレクターは、アクセス可能なテーブルのキーまたは値として表示されるオブジェクトを収集しません。つまり、キーと値はどちらも、参照するオブジェクトの再利用を妨げるため、*強参照* です。弱参照テーブルでは、キーと値が弱参照になる場合があります。つまり、弱参照テーブルには3種類あります:キーが弱参照のテーブル、値が弱参照のテーブル、およびキーと値の両方が弱参照の完全な弱参照テーブルです。テーブルの種類に関係なく、キーまたは値が収集されると、エントリ全体がテーブルから消えます。

テーブルの弱参照の指定は、メタテーブルの `__mode` フィールドによって行われます。このフィールドの値が存在する場合、文字列である必要があります。文字列に文字 `k`(小文字)が含まれている場合、テーブル内のキーは弱参照です。文字列に文字 `v`(小文字)が含まれている場合、テーブル内の値は弱参照です。次の例は人工的ですが、弱参照テーブルの基本的な動作を示しています。

    a = {}
    b = {}
    setmetatable(a, b)
    b.__mode = "k"         -- now `a' has weak keys
    key = {}               -- creates first key
    a[key] = 1
    key = {}               -- creates second key
    a[key] = 2
    collectgarbage()       -- forces a garbage collection cycle
    for k, v in pairs(a) do print(v) end
      --> 2
この例では、2番目の代入 `key = {}` は最初のキーを上書きします。コレクターが実行されると、最初のキーへの他の参照がないため、最初のキーは収集され、テーブル内の対応するエントリが削除されます。ただし、2番目のキーは依然として変数 `key` に固定されているため、収集されません。

弱参照テーブルから収集できるのはオブジェクトのみであることに注意してください。数値やブール値などの値は収集できません。たとえば、前の例のテーブル `a` に数値キーを挿入すると、コレクターによって削除されることはありません。もちろん、数値キーに対応する値が収集されると、エントリ全体が弱参照テーブルから削除されます。

文字列はここで微妙な問題を示します。文字列は収集可能ですが、実装の観点からは、他の収集可能なオブジェクトのようではありません。テーブルや関数などの他のオブジェクトは明示的に作成されます。たとえば、Lua が `{}` を評価するたびに、新しいテーブルが作成されます。`function () ... end` を評価するたびに、新しい関数(実際にはクロージャ)が作成されます。しかし、Lua は `”a”..”b”` を評価すると新しい文字列を作成するのでしょうか?システムに既に文字列 `”ab”` がある場合はどうでしょうか?Lua は新しい文字列を作成するのでしょうか?コンパイラはプログラムを実行する前にその文字列を作成できるのでしょうか?それは問題ではありません。これらは実装の詳細です。したがって、プログラマーの観点からは、文字列は値であり、オブジェクトではありません。したがって、数値やブール値と同様に、文字列は弱参照テーブルから削除されません(関連付けられた値が収集されない限り)。