【Lua】メタテーブルによるクラス風のメンバ関数の定義

プログラマーの尾関です。
以前関わっていたプロジェクトで、Luaのメタテーブルを使ったクラス風のメンバ関数を定義する方法を学んだので、その概念と定義方法を紹介したいと思います。

Luaのメタテーブルとは

Luaは特定の演算子や操作(加算、比較、インデックス参照など)が適用される際に、オブジェクトのメタテーブルを参照します。例えば、加算演算のオペランドを処理する場合、Luaはメタテーブルから "__add" フィールドを調べ、見つかればその関数を呼び出して加算処理を行います。

そして、メタテーブルの取得はgetmetatable()、設定はsetmetatable()を使用します。

このような機能があるため、メタテーブルにメンバ関数を登録することで、クラスのような動作をするオブジェクトを作ることが可能です。

メタテーブルによるクラス風のメンバ関数の定義

コンストラクタの定義

まずはクラスのコンストラクタの定義を作ります。

Foo = {}
function Foo.new(a, b)
  return {a = a, b = b}
end

このように return で {} を返し、その中でキーを定義すると、Foo.new() でクラスのコンストラクタ(とメンバ関数)を定義して呼び出すことができます。

■実行結果

f = Foo.new(10, 20)
f
table: 0x7fffd38ba520
f.a
10
f.b
20

function指定でメンバ関数を定義する

Foo = {}

function Foo.new(a, b)
  return {
    -- ここからメンバ変数の定義.
    a = a,
    b = b,
    -- ここからメンバ関数の定義.
    get_a = function(self) return self.a end,
    get_b = function(self) return self.b end,
    set_a = function(self, x) self.a = x end,
    set_b = function(self, x) self.b = x end
  }
end


return {} がメンバ変数やメンバ関数に該当するので、ここに function() を定義することでメンバ関数となります。

メタテーブルでメンバ関数を定義する

ただこの書き方は可読性が低くなる(インデントが深くなりすぎる)という問題があるので、メタテーブル経由で設定するとわかりやすいコードとなります。

-- クラス定義
Foo = {}

-- メンバ関数の定義
function Foo.get_a(self) return self.a end
function Foo.get_b(self) return self.b end
function Foo.set_a(self, x) self.a = x end
function Foo.set_b(self, x) self.b = x end

-- コンストラクタ
function Foo.new(a, b)
  -- メンバ変数を設定.
  local obj = {a = a, b = b}
  -- メタテーブルからメンバ関数を設定.
  return setmetatable(obj, {__index = Foo})
end

関数 setmetatable(obj, metatable) は obj のメタテーブルを metatable に設定します。返り値は第1 引数の obj です。メタテーブルのフィールド __index に Foo を指定すると、obj で見つからないフィールドは、Foo から探索されるようになります。

なお関数定義は遅延評価されるため、コンストラクタを先に定義しても問題ありません。

-- クラス定義
Foo = {}

-- コンストラクタ
function Foo.new(a, b)
  -- メンバ変数を設定.
  local obj = {a = a, b = b}
  -- メタテーブルからメンバ関数を設定.
  return setmetatable(obj, {__index = Foo})
end

-- 後から定義でも問題なし
-- メンバ関数の定義
function Foo.get_a(self)
  return self.a
end
function Foo.get_b(self)
  return self.b
end
function Foo.set_a(self, x)
  self.a = x
end
function Foo.set_b(self, x)
  self.b = x
end

参考

この記事を書くにあたって、以下のページを参考にさせていただきました。

Follow me!