luabind: невозможно извлечь значения из таблицы, проиндексированной не встроенными классами‏

Я использую luabind 0.9.1 из основного дистрибутива Райана Павлика с Lua 5.1, cygwin на Win XP SP3 + последние патчи x86, boost 1.48, gcc 4.3.4. Lua и boost — это предварительно скомпилированные версии cygwin.

Я успешно создал luabind как в статической, так и в общей версии.

Обе версии проходят все тесты, ЗА ИСКЛЮЧЕНИЕМ теста test_object_identity.cpp, который не проходит в обеих версиях.

Я отследил проблему до следующей проблемы: если запись в таблице создана для НЕ встроенного класса (т. е. не 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! (на самом деле, кажется, ноль)

Однако, если я использую 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 создает НОВЫЙ ОТЛИЧНЫЙ объект КАЖДЫЙ раз, когда вы используете сложное значение в качестве ключа (но это НЕ происходит, если вы используете примитивное значение или объект).

Вот небольшой тестовый пример, демонстрирующий это:

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, а затем заменяется на 7 при доступе через объект ключа. Однако при СНОВА доступе через tp создается новая запись. Вот почему gettable() впоследствии дает сбой.

Спасибо, Дэвид


person qwer1304    schedule 25.04.2012    source источник
comment
вы когда-нибудь решили эту проблему? У меня уже есть эта проблема при использовании значений int в качестве ключей для таблиц, например. local testTable = {[10]=зеленый, [9]=оранжевый, [8]=желтый} - если я использую строки вместо чисел в качестве ключей - все работает нормально - я передаю эту таблицу в качестве параметра функции C++ и я получить ошибку приведения тоже   -  person Steve    schedule 09.07.2012


Ответы (1)


Отказ от ответственности: я не эксперт по luabind. Вполне возможно, что я что-то пропустил в возможностях luabind.

Прежде всего, что делает luabind при преобразовании test_param в ключ Lua? Политика по умолчанию — копирование. Чтобы процитировать документацию 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, которое будет хорошо себя вести как индекс таблицы.
  • Use a different kind of binding. There will be some overhead, but if you want identity, you'll have to live with it. It sounds like luabind has some support for wrappers, which you may need to use to preserve identity:

    Когда указатель или ссылка на зарегистрированный класс с оболочкой передается в Lua, luabind запрашивает его динамический тип. Если динамический тип наследуется от wrap_base, идентификатор объекта сохраняется.

person tehtmi    schedule 24.03.2015