【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
参考
この記事を書くにあたって、以下のページを参考にさせていただきました。