クラス登録は、クラス名でテーブルコンストラクタ関数を登録することによって行われます。テーブルコンストラクタ関数は、テーブルオブジェクトを返すテンプレートクラスの静的メソッドです。
注:静的クラスメンバ関数は、シグネチャが同じであればC関数と互換性があるため、Luaに登録できます。以下のコードスニペットはテンプレートクラスのメンバ関数であり、「T」はバインドされるクラスです。
static void Register(lua_State* L) { lua_pushcfunction(L, &Luna<T>::constructor); lua_setglobal(L, T::className); if (otag == 0) { otag = lua_newtag(L); lua_pushcfunction(L, &Luna<T>::gc_obj); lua_settagmethod(L, otag, "gc"); /* tm to release objects */ } }オブジェクトインスタンス化は、ユーザーがテーブルコンストラクタ関数に渡した引数をC++オブジェクトのコンストラクタに渡し、オブジェクトを表すテーブルを作成し、クラスのメンバ関数をそのテーブルに登録し、最後にテーブルをLuaに返すことによって行われます。オブジェクトポインタは、インデックス0のテーブルにユーザーデータとして格納されます。メンバ関数マップへのインデックスは、各関数に対してクロージャ値として格納されます。メンバ関数マップについては後で詳しく説明します。
static int constructor(lua_State* L) { T* obj= new T(L); /* new T */ /* user is expected to remove any values from stack */ lua_newtable(L); /* new table object */ lua_pushnumber(L, 0); /* userdata obj at index 0 */ lua_pushusertag(L, obj, otag); /* have gc call tm */ lua_settable(L, -3); /* register the member functions */ for (int i=0; T::Register[i].name; i++) { lua_pushstring(L, T::Register[i].name); lua_pushnumber(L, i); lua_pushcclosure(L, &Luna<T>::thunk, 1); lua_settable(L, -3); } return 1; /* return the table object */ }C関数とは異なり、C++メンバ関数は、関数を呼び出すためにクラスのオブジェクトが必要です。メンバ関数呼び出しは、オブジェクトポインタとメンバ関数ポインタを取得し、実際の呼び出しを行う関数によって「サンク」する(間接的に呼び出す)ことによって行われます。メンバ関数ポインタは、クロージャ値によってメンバ関数マップからインデックスされ、オブジェクトポインタはインデックス0のテーブルから取得されます。Luaのすべてのクラス関数は、この関数を使用して登録されることに注意してください。
static int thunk(lua_State* L) { /* stack = closure(-1), [args...], 'self' table(1) */ int i = static_cast<int>(lua_tonumber(L,-1)); lua_pushnumber(L, 0); /* userdata object at index 0 */ lua_gettable(L, 1); T* obj = static_cast<T*>(lua_touserdata(L,-1)); lua_pop(L, 2); /* pop closure value and obj */ return (obj->*(T::Register[i].mfunc))(L); }ガベージコレクションは、テーブルのユーザーデータにガベージコレクションタグメソッドを設定することによって行われます。ガベージコレクタが実行されると、「gc」タグメソッドが呼び出され、オブジェクトが単純に削除されます。 「gc」タグメソッドは、新しいタグを使用してクラス登録時に登録されます。上記のオブジェクトインスタンス化では、ユーザーデータにタグ値が付けられます。
static int gc_obj(lua_State* L) { T* obj = static_cast<T*>(lua_touserdata(L, -1)); delete obj; return 0; }それを踏まえると、クラスが準拠しなければならないいくつかの要件があります。
`Luna
struct RegType { const char* name; const int(T::*mfunc)(lua_State*); };C++クラスをLuaに登録する方法の例を次に示します。`Luna
class Account { double m_balance; public: Account(lua_State* L) { /* constructor table at top of stack */ lua_pushstring(L, "balance"); lua_gettable(L, -2); m_balance = lua_tonumber(L, -1); lua_pop(L, 2); /* pop constructor table and balance */ } int deposit(lua_State* L) { m_balance += lua_tonumber(L, -1); lua_pop(L, 1); return 0; } int withdraw(lua_State* L) { m_balance -= lua_tonumber(L, -1); lua_pop(L, 1); return 0; } int balance(lua_State* L) { lua_pushnumber(L, m_balance); return 1; } static const char[] className; static const Luna<Account>::RegType Register }; const char[] Account::className = "Account"; const Luna<Account>::RegType Account::Register[] = { { "deposit", &Account::deposit }, { "withdraw", &Account::withdraw }, { "balance", &Account::balance }, { 0 } }; [...] /* Register the class Account with state L */ Luna<Account>::Register(L); -- In Lua -- create an Account object local account = Account{ balance = 100 } account:deposit(50) account:withdraw(25) local b = account:balance()Accountインスタンスのテーブルは次のようになります。
0 = userdata(6): 0x804df80 balance = function: 0x804ec10 withdraw = function: 0x804ebf0 deposit = function: 0x804f9c8
サンクメカニズムは、クラスの中核であり、呼び出しを「サンク」します。これは、関数呼び出しが関連付けられているテーブルからオブジェクトポインタを取得し、メンバ関数ポインタのメンバ関数マップをインデックスすることによって行われます。(Luaテーブルの関数呼び出し`table:function()`は、`table.function(table)`の構文糖です。呼び出しが行われると、Luaは最初にテーブルをスタックにプッシュし、次に引数をプッシュします)。メンバ関数のインデックスはクロージャ値であり、最後に(引数の後)スタックにプッシュされます。最初は、オブジェクトポインタをクロージャとして使用していました。つまり、インスタンス化されたクラスごとに、関数ごとにオブジェクトポインタ(void*)とメンバ関数のインデックス(int)の2つのクロージャ値を持つことになります。これはかなりコストがかかると考えられましたが、オブジェクトポインタへの迅速なアクセスが可能です。また、ガベージコレクションのためにテーブル内のユーザーデータオブジェクトが必要でした。最終的に、オブジェクトポインタのテーブルをインデックスしてリソースを節約し、その結果、関数呼び出しのオーバーヘッド(オブジェクトポインタのテーブルルックアップ)が増加しました。
すべての事実を考慮すると、この実装では、メンバ関数のインデックスを保持するためのクロージャ、ガベージコレクションのための「gc」タグメソッド、およびテーブルコンストラクタとメンバ関数呼び出しのための関数登録など、Luaで使用可能な拡張メカニズムのごく一部のみを使用しています。
なぜシグネチャ`int(T::*)(lua_State*)`を持つメンバ関数のみを登録できるようにするのでしょうか?これにより、メンバ関数はLuaと直接やり取りできます。Luaから引数を取得して値を返し、Lua API関数を呼び出すなどです。さらに、登録されたC関数と同一のインターフェースを提供するため、C++を使用したいユーザーにとって容易になります。
オブジェクトは作成時に単に`new`を使用して作成されます。オブジェクトの作成方法に関するより詳細な制御をユーザーに提供する必要があります。たとえば、ユーザーはシングルトンクラスを登録したい場合があります。1つの解決策として、オブジェクトへのポインタを返す静的`create()`メンバ関数をユーザーが実装することです。このようにして、ユーザーはシングルトンクラスを実装したり、単に`new`を使用してオブジェクトを割り当てたり、その他のアクションを実行できます。オブジェクトポインタを取得するために、`new`ではなく`create()`を呼び出すように`constructor`関数を変更できます。これにより、クラスに多くのポリシーが追加されますが、はるかに柔軟性が高くなります。ガベージコレクション用の「フック」も一部のユーザーには役立つ可能性があります。
約70行のソースコードからなるテンプレートクラスの完全なソースコードは、Luaのアドオンページから入手できます。