Problems with wrapping Lua into a C++ class

Viewed 104

I'm using the Lua C API in C++ and I'm wrapping it into a class as follows:

class LuaScripting {
public:
    lua_State *lua;

    LuaScripting();

    ~LuaScripting();

    bool execute_script(const std::string &script);
};

LuaScripting::LuaScripting() {
    lua = luaL_newstate(); /* Opens Lua */
    luaL_openlibs(lua); /* Opens the standard libraries */

    /* Register our custom function(s) */
    lua_register(lua, "write8", lua_write8);
    lua_register(lua, "write16", lua_write16);
    lua_register(lua, "write32", lua_write32);
    lua_register(lua, "read8", lua_read8);
    lua_register(lua, "read16", lua_read16);
    lua_register(lua, "read32", lua_read32);
    lua_register(lua, "math_sin", math_sin);
}

LuaScripting::~LuaScripting() {
    lua_close(lua); /* Clean up lua */
}

I'm testing it as follows:

int main() {
    // Disable buffering
    setbuf(stdout, nullptr);

    LuaScripting lua_scripting;
    testWritingInt8(lua_scripting);
    testWritingInt16(lua_scripting);
    testWritingInt32(lua_scripting);
    testReadingInt32(lua_scripting);
    test_math_sin(lua_scripting);

    return EXIT_SUCCESS;
}

The problems I'm having:

  • After the first testWritingInt8() the class destructor is called which will run lua_close(lua) even though the class instance didn't fall out of scope, yet. I'm not using any threads. Why is this happening?
  • When calling lua_close(lua) the program crashes, why?
  • After commenting out lua_close(lua) the write test cases run successfully but the readXX() or math_sin() return with an empty stack despite pushing a value onto the stack. Why?

Implementation:

static int math_sin(lua_State *lua) {
    const auto value = luaL_checknumber(lua, 1);
    const auto sine_result = sin(value);
    lua_pushnumber(lua, sine_result);
    return 1;
}

Test case:

void test_math_sin(LuaScripting &lua_scripting) {
    std::stringstream lua_script_builder;
    const auto target_value = 90.f;
    lua_script_builder << "math_sin(" << target_value << ")";
    const auto script_result = lua_scripting.execute_script(lua_script_builder.str());
    assert(script_result == LUA_OK);
    // TODO Not working
    const auto read_value = (int32_t) lua_tonumber(*lua_scripting.lua, -1);
    assert(target_value == read_value);
}

I'm always using the same lua_State * and I only call luaL_newstate() once.

As an attempted fix I tried to declare the lua state as a non-pointer:

lua_State lua; // error: aggregate ‘lua_State lua’ has incomplete type and cannot be defined

But doing this does not compile.

Adding another level of indirection via lua_State **lua fixes the crashing problem with lua_close() but does not fix any of the other issues.

1 Answers

That's because you are copying the LuaScripting object. I bet testWritingInt8 is declared like this:

void testWritingInt8(LuaScripting lua)

Notice the lua parameter is not a pointer or a reference to a LuaScripting object, it is a LuaScripting object.

So when you call testWritingInt8(lua), the computer copies the LuaScripting object into a new one, calls the function, and destroys the new one at the end the call.

Now, why does that crash? Well, your LuaScripting class doesn't have a copy constructor (LuaScripting(const LuaScripting &)) so the compiler creates a default one, which just copies all the member variables - in this case the lua pointer is copied. Then, when the new object is destroyed, it frees the Lua state.

Solution: Make it so LuaScripting objects can't be copied accidentally, by deleting the copy constructor. Also the assignment operator.

// inside LuaScripting
LuaScripting(const LuaScripting &) = delete;
LuaScripting &operator =(const LuaScripting &) = delete;

Then make sure to pass LuaScripting values by reference.

If you want to be able to move LuaScripting objects around - say, if you want to store them in a vector, but still not copy them - you can define a move constructor and move assignment operator, which are out of scope of this answer.


Your math_sin test case doesn't return any values on the stack because... your script doesn't return any values. Try return math_sin(target_value) instead of just math_sin(target_value).

Related