Как зарегистрировать конструктор класса C++ в пользовательских данных Lua и использовать его по умолчанию

Используя Lua C API, я зарегистрировал простой класс Object в Lua, например:

// My C++ Object class
class Object {
private:
    double x;
public:
    Object(double x) : x(x){}
};

// Create and return instance of Object class to Lua
int object_new(lua_State* L)
{
    double x = luaL_checknumber(L, 1);
    *reinterpret_cast<Object**>(lua_newuserdata(L, sizeof(Object*))) = new Object(x);
    luaL_setmetatable(L, "Object");
    return 1;
}

// Functions to register to Lua
const luaL_Reg functions[] =
{
    {"new", object_new},
    {nullptr, nullptr}
};

// Register the Object class to Lua
luaL_newmetatable(L, "Object");
luaL_setfuncs(L, functions, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");

В моем скрипте Lua отлично работает следующее:

// Works!
my_object = Object.new(42)

Но я хотел бы иметь возможность сделать это (т.е. опустить часть .new):

// Fail :(
my_object = Object(42)

Но когда я запускаю сценарий Lua, я получаю эту ошибку:

...attempt to call a table value (global 'Object').

Есть ли способ зарегистрировать класс C++ таким образом, чтобы вызывался конструктор, если мы не указываем имя функции? Что я пропустил, чтобы сделать эту работу? Это было бы особенно полезно для временных объектов.

Спасибо!


person Deathicon    schedule 23.02.2016    source источник
comment
C не имеет ни классов, ни конструкторов.   -  person πάντα ῥεῖ    schedule 24.02.2016
comment
Конечно, просто добавьте метаметод __call в свою метатаблицу Object.   -  person Joel Cornett    schedule 24.02.2016


Ответы (2)


Вы должны проверить возврат luaL_newmetatable, чтобы зарегистрировать свои метаметоды только один раз.

Вы можете заменить luaL_setmetatable на luaL_newmetatable, чтобы ваш код был совместим с Lua 5.1, вы можете встроить метатабличную регистрацию в конструктор, и он будет работать так же (за исключением дополнительного lua_setmetatable).

Для конструктора просто зарегистрируйте функцию. Метатаблица должна управлять экземпляром, а не его созданием.

Не забудьте добавить деконструктор (__gc), чтобы освободить выделенный экземпляр класса C++.

В конце вам нужно просто зарегистрировать функцию Object, которая создает метатаблицу при первом вызове.

#define LUA_META_OBJECT "Object"

class Object {
private:
    double x;
public:
    Object(double x) : x(x){}
};

static int object_free(lua_State* L)
{
    delete *static_cast<Object**>(luaL_checkudata(L, 1, LUA_META_OBJECT));
    return 0;
}

static int object_new(lua_State* L)
{
    const lua_Number x = luaL_checknumber(L, 1);
    *static_cast<Object**>(lua_newuserdata(L, sizeof(Object*))) = new Object(x);
    if(luaL_newmetatable(L, LUA_META_OBJECT)){
        static const luaL_Reg functions[] =
        {
            {"__gc", object_free},
            {nullptr, nullptr}
        };
        luaL_setfuncs(L, functions, 0);
        lua_pushvalue(L, -1);
        lua_setfield(L, -2, "__index");
    }
    lua_setmetatable(L, -2);
    return 1;
}

...
    lua_register(L, "Object", object_new);
...
person Youka    schedule 24.02.2016
comment
@Rochet2 In both cases pushes onto the stack the final value associated with tname in the registry. Метатаблица всегда нажимается! Просто определение метатаблицы происходит один раз, потому что дальнейшие вызовы luaL_newmetatable возвращают 0. - person Youka; 24.02.2016
comment
Я не говорил о проталкивании метатаблицы. Я говорил о творении. Метатаблица никогда не создается, потому что уже существует ключ с именем Object, который является функцией, которую вы определяете внизу. - person Rochet2; 24.02.2016
comment
@ Rochet2 Вы когда-нибудь тестировали мой код или правильно читали руководство? Существует разница между глобальной средой и реестр! lua_register устанавливает функцию в таблицу окружения для использования в Lua, luaL_newmetatable устанавливает метатаблицу в таблицу реестра для использования в C. Замены нет. - person Youka; 24.02.2016
comment
Ах да, это ключевое отличие. Спасибо! - person Rochet2; 24.02.2016
comment
@Rochet2 отличает ли это ваш ответ от того, что сказала Юка? - person Deathicon; 25.02.2016
comment
@Deathicon Наши ответы используют разные методы, но достигают одной и той же цели. В моем коде метатаблица Object отображается в lua, а другая метатаблица используется для получения того, что вы хотели. В ответе Youka метатаблица, используемая для пользовательских данных, недоступна из lua (поправьте меня, если я ошибаюсь), и функция с именем Object предназначена для получения того, что вы хотели. Ответ Youka может быть более эффективным и безопасным, поскольку функция вызывается напрямую и не предоставляет метатаблицу для сценариев, с которыми можно возиться. - person Rochet2; 25.02.2016

Мне очень нравится ответ Youka, но я хотел реализовать это по-другому. Большая часть кода здесь заимствована из ответа Youka.

Youka также сделал важное замечание, что вам необходимо создать метаметод __gc, чтобы у вас не было утечки памяти, когда ваши экземпляры Object выходят за пределы области видимости.

В приведенном ниже коде я сделал отдельную функцию, которая помещает метатаблицу в стек и создает ее, если она еще не существует. Сама метатаблица имеет метатаблицу с метаметодом __call, который вызывает создание объекта. Такое поведение должно позволить вам использовать o = Object(42) для создания новых объектов. Я также включил код, чтобы сохранить старую функциональность, используя o = Object.new(42). В нижней части кода метатаблица помещается один раз для первоначального создания метатаблицы.

#define LUA_META_OBJECT "Object"

class Object {
private:
    double x;
public:
    Object(double x) : x(x){}
};

// declaration so we can use this in object_new function
int push_object_metatable(lua_State* L);

static int object_free(lua_State* L)
{
    delete *static_cast<Object**>(luaL_checkudata(L, 1, LUA_META_OBJECT));
    return 0;
}

static int object_new(lua_State* L)
{
    const lua_Number x = luaL_checknumber(L, 1);
    *static_cast<Object**>(lua_newuserdata(L, sizeof(Object*))) = new Object(x);
    push_object_metatable(L);
    lua_setmetatable(L, -2);
    return 1;
}

static int call_object_new(lua_State* L)
{
    lua_remove(L, 1);
    object_new(L);
    return 1;
}

// Pushes the metatable for Object and creates if it doesnt exist yet
int push_object_metatable(lua_State* L)
{
    if(luaL_newmetatable(L, LUA_META_OBJECT)){
        static const luaL_Reg functions[] =
        {
            {"new", object_new},
            {"__gc", object_free},
            {nullptr, nullptr}
        };
        luaL_setfuncs(L, functions, 0);
        lua_pushvalue(L, -1);
        lua_setfield(L, -2, "__index");

        // Set a metatable for the metatable :D
        // This allows using Object(42) to make new objects
        lua_newtable(L);
        lua_pushcfunction(L, call_object_new);
        lua_setfield(L, -2, "__call");
        lua_setmetatable(L, -2);
    }
    return 1;
}

...
    // Register Object metatable for lua (Create and push it)
    push_object_metatable(L);
    lua_setglobal(L, LUA_META_OBJECT);
...
person Rochet2    schedule 24.02.2016
comment
Мне нравится ваше решение, но в конце концов я предпочел ответ Youka, чтобы подчеркнуть разницу между тем, как я создаю объекты userdata и объекты lightuserdata. Спасибо! - person Deathicon; 26.02.2016