Wednesday, February 18, 2015

LuaのC++バインドをラムダ式で書く

以前LuaからC++のクラスを使えるように自前バインドを書きました。しかし、非常に冗長でした。規模の大きいクラスになると恐ろしい長さになりそうです。そこで、C++11の恩恵に与りながら短く書き直してみました。

#include <new>
class MyClass
{
public:
MyClass()
{
value = 0;
puts("MyClass::MyClass called");
}
~MyClass()
{
puts("MyClass::~MyClass called");
}
int value;
};
static const char* myClassName = "MyClass";
#define GET_MYCLASS \
MyClass* p = (MyClass*)luaL_checkudata(L, 1, myClassName); \
if (!p) { return 0; }
static int MyClassNew(lua_State *L)
{
MyClass* p = new (lua_newuserdata(L, sizeof(MyClass))) MyClass;
luaL_getmetatable(L, myClassName);
lua_setmetatable(L, -2);
return 1;
}
void BindMyClass()
{
int r = luaL_newmetatable(L, myClassName);
assert(r);
lua_pushstring(L, "__index");
lua_pushvalue(L, 1);
lua_settable(L, -3);
static struct luaL_Reg methods[] =
{
{ "__gc", [](lua_State* L) { GET_MYCLASS p->~MyClass(); return 0; } },
{ "SetValue", [](lua_State* L) { GET_MYCLASS p->value = (int)lua_tointeger(L, -1); return 0; } },
{ "GetValue", [](lua_State* L) { GET_MYCLASS lua_pushinteger(L, p->value); return 1; } },
{ nullptr, nullptr },
};
luaL_setfuncs(L, methods, 0);
lua_pop(L, 1);
lua_register(L, "MyClass", MyClassNew);
}
まず、luaL_setfuncsで複数関数を一気に登録します。その際luaL_Regの配列を渡すのですが、関数部をラムダ式で書きます。MyClassのポインタはGET_MYCLASSというマクロで取得し、更に万一型が合わないなどで取得に失敗してもクラッシュしないようになっています。

また、placement newでMyClassのメモリ確保をluaに任せるようにします。これによってダブルポインタを使う無駄を省いた上にコードもすっきりしました。deleteのかわりにp->~MyClassを明示的に呼ぶ必要があります。placement newを使うためには #include <new>が必要です。

単独のサンプルとしてはこのくらいが最短かと思いますが、複数のクラスをバインドするなら上のコードの一部を共通化できるのでもう少し短くなりそうです。例えばメタテーブルを生成して__indexに自身を指定するあたりはサブルーチン化します。

MyClassNewはマクロ化すれば複数クラスで使いまわせそうです。本当はテンプレート関数化したいところですが、メタテーブルに渡す文字列をテンプレート引数にするにはややトリックが必要です。現状のC++では文字列定数をテンプレート引数に出来ないためです。


No comments:

Post a Comment