4

C++ で、構造のようなバイナリ ツリーを作成するクラスがあり、それを次のように使用するとします。

CTreeRoot* root = new CTreeRoot(/* whatever */);
CNode* leftNode = root->getLeftNode();
CNode* rightNode = root->getRightNOde();

leftNode->doSomething();
rightNode->doSomething();

// etc

また、左右のノードには独自の左右のノードがあるとします (したがって、バイナリ ツリー)。これを Lua に公開して ( luabind を使用せずに)、同じようなことができるようにします。

local root = treeroot.new(/* whatever */)
local left = root:getLeftNode()
local right = root:getRightNode()

left:doSomething();
right:doSomething(); 

私はそれのほとんどを機能させました。ただし、getLeftNode() および getRightNode() メソッドについては、「間違っている」と確信しています。たとえば、C++で getLeftNode() を実装する方法は次のとおりです。

int MyLua::TreeRootGetLeftNode(luaState* L)
{
    CTreeRoot* root = (CTreeRoot*)luaL_checkudata(L, 1, "MyLua.treeroot");
    CNode* leftNode = root->getLeftNode();

    if (leftNode != NULL)
    {
        int size = sizeof(CNode);

        // create a new copy of the CNode object inplace of the memory Lua
        // allocated for us
        new ((CNode*)lua_newuserdata(L,size)) CNode((const CNode&)*leftNode);
        lua_setmetatable(L, "MyLua.treenode");
    }
    else
    {
        lua_pushnil(L);
    }

    return 1;
}

ユーザーデータを CTreeRoot オブジェクトにキャストし、getLeftNode() を呼び出し、それが存在することを確認してから (これが「間違った部分」です)、返すオブジェクトをコピーするコピー コンストラクターを使用して別のユーザーデータ データ オブジェクトを作成します。

これは、このタイプのシナリオの「標準的な方法」ですか? 本当に必要なのは既存のオブジェクトへの参照だけなので、オブジェクトの別のコピーを作成することは避けたいと思われます。

新しいオブジェクトを作成したくないので、これは lightuserdata に最適な場所のように思えます。既に存在するオブジェクトを返すことができれば幸いです。ただし、問題は、lightuserdata にはメタ テーブルがないため、オブジェクトを元に戻すと、オブジェクトが役に立たなくなることです。基本的に、私は次のようなことをしたい:

int MyLua::TreeRootGetLeftNode(luaState* L)
{
    CTreeRoot* root = (CTreeRoot*)luaL_checkudata(L, 1, "MyLua.treeroot");
    CNode* leftNode = root->getLeftNode();

    if (leftNode != NULL)
    {
        // "WRONG" CODE BUT SHOWS WHAT I WISH I COULD DO
        lua_pushlightuserdata(L, (void*)leftNode);
        lua_setmetatable(L, "MyLua.treenode");
    }
    else
    {
        lua_pushnil(L);
    }

    return 1;
}

MyLua::TreeRootGetLeftNodeLua でオブジェクトを「オブジェクト」として使用できるように、すでに存在するオブジェクトのコピーをメソッドで Lua に返す方法を教えてください。

4

1 に答える 1

2

ここで実行できるメモリ最適化には 2 つのレベルがあります。最初の機能的だが非効率的なソリューションでは、ユーザーが を呼び出したときに、Lua ユーザーデータに保存するためにgetLeftNode()のコピーを作成する必要があります。CNodeさらに、ユーザーが同じツリーで繰り返し呼び出すたびに、以前に作成されたものであっても、getLeftNode()を表す新しいユーザーデータを作成し続けます。CNode

最適化の最初のレベルでは、この呼び出しをメモ化して、ユーザーが同じサブツリーを要求するたびに、同じものを表す別のユーザーデータをコピーして構築する代わりに、最初に作成されたユーザーデータを返すことができるようにすることができます。ただし、Lua インターフェースを変更するか、C++ 実装を変更するか、または単に弾丸を噛むかによって、これには 3 つのアプローチがあります。

  • MyLua.treenodeuserdata には現在、オブジェクトの実際のデータが含まれていますCNode。ただし、これは残念なことです。つまり、CNodeオブジェクトを作成するたびに、配置 new を使用して、作成直後に Lua によって割り当てられたメモリにオブジェクトを格納する必要があるからです。CNode*おそらくより良いのは、代わりに ( ) のユーザーデータにポインタを格納することですMyLua.treenodeMyLua.treenodeこれには、データをオブジェクトへのポインターと見なすように、Lua インターフェースを変更する必要がありCNodeます。

  • ユーザーデータに直接CNodeデータを格納したい場合は、 を作成するときに、配置 new を使用して、毎回 Lua によって割り当てられたメモリから s を構築するようにする必要があります (または、アロケーター パターンを使用することもできます)。 C++ 標準ライブラリで使用されていますか?)。ただし、実装が Lua ランタイムに依存するようになったため、これはあまり洗練されていません。これはお勧めしません。MyLua.treenodeCTreeRootCNodeCNode

  • 上記の解決策のいずれも適切でない場合は、サブノードを返すたびにコピーを作成する必要がありますが、同じユーザーデータを作成したかどうかを追跡することで、同じノードで繰り返し呼び出しの効率を向上させることができます。前に (たとえば、Lua テーブルを使用して)。

最適化の第 2 レベルでは、コピーしたサブツリーを元のツリーのフラグメントへの弱い参照にすることで、メモリをさらに節約できます。このように、サブツリーをコピーするときはいつでも、元のツリーの一部へのポインターを作成しているだけです。または、元のツリーが破棄された後でもサブツリーを維持したい場合は、強い参照を使用できますが、参照カウントの詳細を詳しく説明する必要があります。どちらの場合も、この最適化は純粋に C++ レベルで行われ、Lua インターフェースとは関係ありません。コードから判断すると、既に弱参照 ( CNode*) を使用していると思います。

注:内部実装で使用する場合を除いて、軽いユーザーデータはおそらく避けるのが最善です。その理由は、軽いユーザーデータは本質的に C ポインターと同等であり、したがって、ほとんどすべてを指すことができるからです。ライト ユーザーデータを Lua コードに公開すると、ライト ユーザーデータがどこから来たのか、または含まれているデータの種類がわからず、それを使用するとセキュリティ リスクが発生します (プログラムのセグメンテーション違反の可能性もある)。ライト ユーザー データを使用する適切な方法は、Lua レジストリに格納されている Lua ルックアップ テーブルのインデックスとして使用することです。これを使用して、前述のメモ化を実装できます。

于 2013-02-09T19:06:04.087 に答える