【问题标题】:luabind: cannot retrieve values from table indexed by non-built-in classes‏luabind:无法从由非内置类索引的表中检索值
【发布时间】:2012-05-06 04:40:31
【问题描述】:

我正在使用 Ryan Pavlik 的主发行版中的 luabind 0.9.1 和 Lua 5.1、Win XP SP3 上的 cygwin + 最新补丁 x86、boost 1.48、gcc 4.3.4。 Lua 和 boost 是 cygwin 预编译的版本。

我已经成功构建了静态和共享版本的 luabind。

两个版本都通过了所有测试,除了 test_object_identity.cpp 测试在两个版本中都失败了。

我已将问题追溯到以下问题: 如果表中的条目是为 NON 内置类创建的(即不是 int、string 等),则无法检索该值。

这里有一段代码可以证明这一点:

#include "test.hpp"
#include <luabind/luabind.hpp>
#include <luabind/detail/debug.hpp>

using namespace luabind;

struct test_param
{
    int obj;
};

void test_main(lua_State* L)
{
    using namespace luabind;

    module(L)
    [
        class_<test_param>("test_param")
            .def_readwrite("obj", &test_param::obj)
    ];

    test_param temp_object;
    object tabc = newtable(L);
    tabc[1] = 10;
    tabc[temp_object] = 30;

    TEST_CHECK( tabc[1] == 10 );              // passes
    TEST_CHECK( tabc[temp_object] == 30 );    // FAILS!!!

}

tabc[1] 确实是 10 而 tabc[temp_object] 不是 30! (其实好像是nil)

但是,如果我使用 iterate 来检查 tabc 条目,则有两个条目具有正确的键/值对。

有什么想法吗?

顺便说一句,像这样重载 == 运算符:

#include <luabind/operator.hpp>

struct test_param
{
    int obj;
    bool operator==(test_param const& rhs) const
    {
        return obj == rhs.obj;
    }
};

module(L)
    [
        class_<test_param>("test_param")
            .def_readwrite("obj", &test_param::obj)
            .def(const_self == const_self)
    ];

不改变结果。

我还尝试从 [] 运算符切换到 settable() 和 gettable()。结果是一样的。我可以通过调试器看到调用了密钥的默认转换,所以我猜错误来自其中的某个地方,但我无法弄清楚究竟是什么问题。

正如下面简单的测试用例所示,Luabind 对复杂类型的转换肯定存在错误:

struct test_param : wrap_base 
{ 
    int obj; 
    bool operator==(test_param const& rhs) const 
    { return obj == rhs.obj ; } 
}; 

void test_main(lua_State* L) 
{ 
    using namespace luabind; 
    module(L) 
    [ 
        class_<test_param>("test_param") 
                .def(constructor<>()) 
                .def_readwrite("obj", &test_param::obj) 
                .def(const_self == const_self) 
    ]; 

    object tabc, zzk, zzv; 
    test_param tp, tp1; 
    tp.obj = 123456; 
    // create new table 
    tabc = newtable(L); 
    // set tabc[tp] = 5; 
    //         o     k   v 
    settable( tabc,  tp, 5); 
    // get access to entry through iterator() API 
    iterator zzi(tabc); 
    // get the key object 
    zzk = zzi.key(); 
    // read back the value through gettable() API 
    //              o     k 
    zzv = gettable(tabc, zzk);   
    // check the entry has the same value 
    // irrespective of access method 
    TEST_CHECK ( *zzi == 5 && 
                 object_cast<int>(zzv) == 5 ); 
    // convert key to its REAL type (test_param) 
    tp1 = object_cast<test_param>(zzk); 
    // check two keys are the same 
    TEST_CHECK( tp == tp1 ); 
    // read the value back from table using REAL key type 
    zzv = gettable(tabc, tp1); 
    // check the value 
    TEST_CHECK( object_cast<int>(zzv) == 5 ); 
    // the previous call FAILS with 
    // Terminated with exception: "unable to make cast" 
    // this is because gettable() doesn't return 
    // a TRUE value, but nil instead 
} 

希望比我聪明的人能解决这个问题, 谢谢

我已经将问题追溯到这样一个事实,即每次您使用复杂值作为键时,Luabind 都会创建一个 NEW DISTINCT 对象(但如果您使用原始值或对象则不会)。

这里有一个小测试用例来证明这一点:

struct test_param : wrap_base
{
    int obj;
    bool operator==(test_param const& rhs) const
    { return obj == rhs.obj ; }
};

void test_main(lua_State* L)
{
    using namespace luabind;

    module(L)
    [
        class_<test_param>("test_param")
            .def(constructor<>())
            .def_readwrite("obj", &test_param::obj)
            .def(const_self == const_self)
    ];

    object tabc, zzk, zzv;
    test_param tp;
    tp.obj = 123456;
    tabc = newtable(L);
    //         o     k   v
    settable( tabc,  tp, 5);
    iterator zzi(tabc), end;
    std::cerr << "value = " << *zzi << "\n";
    zzk = zzi.key();
    //         o     k    v
    settable( tabc,  tp,  6);
    settable( tabc,  zzk, 7);
    for (zzi = iterator(tabc); zzi != end; ++zzi)
    {
        std::cerr << "value = " << *zzi << "\n";
    }
}

注意 tabc[tp] 首先是 5,然后在通过 key 对象访问时被 7 覆盖。但是,当通过 tp 再次访问时,会创建一个新条目。这就是 gettable() 随后失败的原因。

谢谢, 大卫

【问题讨论】:

  • 您解决过这个问题吗?当使用 int 值作为表的键时,我已经遇到了这个问题,例如local testTable = { [10]="green", [9]="orange", [8]="yellow" } - 如果我使用字符串而不是数字作为键 - 它工作正常 - 我将此表作为参数到一个 C++ 函数,我也得到了转换错误

标签: c++ lua luabind


【解决方案1】:

免责声明:我不是 luabind 方面的专家。我完全有可能错过了一些关于 luabind 的功能。

首先,将 test_param 转换为 Lua 键时,luabind 是做什么的?默认策略是复制。引用 luabind 文档:

这将制作参数的副本。这是按值传递参数时的默认行为。请注意,这只能在从 C++ 传递到 Lua 时使用。此策略要求参数类型具有可访问的复制构造函数。

实际上,这意味着 luabind 将创建一个新对象(称为“完整用户数据”),该对象归 Lua 垃圾收集器所有,并将您的结构复制到其中。这是一件非常安全的事情,因为你用 c++ 对象做什么已经不再重要了。 Lua 对象会在没有任何开销的情况下一直存在。这是对按值类型的对象进行绑定的好方法。

为什么每次将 luabind 传递给 Lua 时都会创建一个新对象?好吧,它还能做什么?传递的对象的地址是否相同都没有关系,因为原始的 c++ 对象在第一次传递给 Lua 后可能已经更改或被销毁。 (记住,它是按值复制到 Lua 的,而不是通过引用。)所以,只有 ==,luabind 必须维护一个列表,列出曾经传递给 Lua 的每个类型的对象(可能是弱)并比较你的反对每一个,看看它是否匹配。 luabind 不这样做(我也不认为应该这样做)。

现在,让我们看看 Lua 方面。即使 luabind 创建了两个不同的对象,它们仍然是相等的,对吧?嗯,第一个问题是,除了某些内置类型之外,Lua 只能通过引用来保存对象。我之前提到的每个“完整用户数据”实际上都是一个指针。这意味着它们并不相同。

但如果我们定义一个 __eq 元操作,它们是相等的。不幸的是,Lua 本身根本不支持这种情况。无论如何,用作表键的用户数据总是按身份进行比较。这实际上对于用户数据并不特别;表格也是如此。 (请注意,为了正确支持这种情况,Lua 需要覆盖对象上的哈希码操作,除了 __eq。Lua 也不支持覆盖哈希码操作。)我不能代表 Lua 的作者为什么他们不这样做允许这样做(之前已经建议过),但确实如此。

那么,有哪些选择呢?

  • 最简单的方法是将 test_param 转换为一个对象(显式地),然后两次都使用该对象来索引表。但是,我怀疑虽然这修复了您的玩具示例,但在实践中并没有太大帮助。
  • 另一种选择就是不使用键等类型。其实我觉得这是一个很好的建议,因为这种轻量级的绑定还是很有用的,唯一的选择就是放弃它。
  • 看起来您可以为您的类型定义自定义转换。在您的示例中,将您的类型转换为 Lua 数字可能是合理的,该数字将作为表索引表现良好。
  • 使用不同类型的绑定。会有一些开销,但如果你想要身份,你就必须忍受它。听起来 luabind 对包装器有一些支持,您可能需要使用它来保留身份:

    当一个带有包装器的注册类的指针或引用被传递给 Lua 时,luabind 将查询它的动态类型。如果动态类型继承自 wrap_base,则保留对象标识。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-11-14
    • 1970-01-01
    • 2021-05-28
    • 1970-01-01
    • 2020-10-12
    • 1970-01-01
    • 1970-01-01
    • 2020-07-24
    相关资源
    最近更新 更多