2013-02-09 54 views
4

在C++,說我有一個創建一個二叉樹狀結構的一類,我用它是這樣的:回到已有的C++對象到Lua

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::TreeRootGetLeftNode方法的返回到Lua已經存在這樣的對象,我可以使用該對象的副本Lua中的「對象」?

回答

2

這裏可以執行兩級內存優化。在您的第一個功能效率低下的解決方案中,當用戶撥打getLeftNode()時,必須創建一個CNode的副本,以便將其存儲在Lua用戶數據中。此外,每當用戶在同一棵樹上重複呼叫getLeftNode()時,它將繼續創建新的用戶數據來表示CNode,即使它以前已經創建過。

在優化的第一個層次,你可以memoize的這一呼籲每次這樣對於相同的子樹的用戶請求,你可以簡單地返回最初創建的,而不是複製和構建另一個用戶數據來表示的用戶數據一樣。有3種方法可以解決這個問題,這取決於你是否想修改Lua接口,修改C++實現,或者只是咬緊牙關。

  • MyLua.treenode用戶數據目前包含了一個CNode對象的實際數據。然而,這是不幸的,因爲這意味着每當你創建一個對象時,你必須使用placement new將它存儲到由Lua分配的內存中,並在創建時立即生成。可能更好的方法是在MyLua.treenode的用戶數據中簡單地存儲指針(CNode*)。這確實需要您修改MyLua.treenode的Lua接口,以便它現在將其數據視爲指向CNode對象的指針。

  • 如果你寧願存儲在MyLua.treenode用戶數據的CNode數據直接,那麼你必須確保當您創建CTreeRoot,它將使用安置新來從每一次的Lua分配的內存構建CNode小號(或者您可以使用C++標準庫中使用的allocator pattern?)。這是不太優雅的,但是現在你的CNode實現依賴於Lua運行時,我不推薦這樣做。

  • 如果沒有以上的解決方案是恰當的,那麼你就只需要做出一個副本,只要你返回一個子節點,但你仍然可以通過跟蹤改進爲同一節點上重複呼叫的效率無論您是否曾經創建過相同的用戶數據(例如使用Lua表)。

在優化的第二級,你還可以通過使您的複製子樹是一個弱引用原來的樹的片段節省內存。這樣,每當你複製一個子樹,你只是創建一個指向原始樹的一部分的指針。或者,如果你想讓你的子樹在原樹被銷燬後仍然存在,那麼你可以使用一個強有力的引用,但是你必須進入引用計數的血腥細節。無論哪種情況,這種優化純粹在C++級別上,並且與Lua接口無關,並且從您的代碼判斷,我假設您已經使用弱引用(CNode*)。

說明:最好避免使用輕量級用戶數據,除非在內部實現中使用。原因是light userdata基本上等同於C指針,因此可以指向任何東西。如果你將light userdata暴露給Lua代碼,你將不知道light userdata可能來自哪裏或者它包含什麼類型的數據,使用它會帶來安全風險(以及對程序進行segfault的可能性)。使用輕量用戶數據的一種方式是將其用作存儲在Lua註冊表中的Lua查找表的索引,其可以用於實現前面提到的記憶。