Search code examples
c++luashared-ptrweak-ptr

Is it valid to static_cast 'weak_ptr<SomeThing>*' to 'void*' then back to 'weak_ptr<void>*' , or do I need to use static_pointer_cast?


I'm embedding Lua scripting in my program, and part of that is allowing Lua to manage the lifetime of some shared and weak pointers. I do that by constructing a pointer in some Lua managed memory (called 'userdata'). At a later time when Lua is about to garbage collect that memory, it calls back to my code with a void* to the memory. At that point, I can cast the void* back to a weak_ptr and call the reset() method before the memory gets freed.

My question is, can I get away with using the same garbage collection callback (__gc metamethod in lua-speak) for every type of weak_ptr? This would necessitate static_cast'ing directly from void* to weak_ptr<void>*, even though I constructed a weak_ptr<something> at that memory location. Is that valid?

Here's the relavent snips of code:

void* p = lua_newuserdatauv(L,sizeof(std::weak_ptr<asio::io_service::strand>),0);
new(p) std::weak_ptr<asio::io_service::strand>(pSyncStrand);
luaL_getmetatable(L, "std_weak_ptr");
lua_setmetatable(L, -2);

And here is the metatable __gc method that is called with the void* (p above):

luaL_newmetatable(L,"std_weak_ptr");
lua_pushcfunction(L, [](lua_State* const L) -> int
    {
        auto weak_ptr = static_cast<std::weak_ptr<void>*>(lua_touserdata(L,1));
        weak_ptr->reset();
        return 0;
    });
lua_setfield(L,-2,"__gc");

Or will I need a separate metatable for every variant of weak_ptr? My tests seem to indicate the above code works, but I want to make sure it's valid and not UB or implementation dependent.

EDIT: Here's my updated code based on Remy Lebeau's answer

The __gc routine is updated to destroy, not just reset, the weak_ptr

luaL_newmetatable(L,"std_weak_ptr_void");
lua_pushcfunction(L, [](lua_State* const L) -> int
    {
        auto weak_ptr = static_cast<std::weak_ptr<void>*>(lua_touserdata(L,1));
        std::destroy_at(weak_ptr);
        return 0;
    });
lua_setfield(L,-2,"__gc");

And here is how I store the pointer now

void* p = lua_newuserdatauv(L,sizeof(std::weak_ptr<void>),0);
new(p) std::weak_ptr<void>(pSyncStrand);
luaL_getmetatable(L, "std_weak_ptr_void");
lua_setmetatable(L, -2);

And as an addition, this is how I use it in the meantime

auto ppSync = static_cast<std::weak_ptr<void>*>(lua_touserdata(L, lua_upvalueindex(1)));
auto pSync = std::static_pointer_cast<asio::io_service::strand>(ppSync->lock());

Solution

  • It is UB to convert a given pointer to a void* and then convert the void* to a different pointer type.

    weak_ptr<something> and weak_ptr<void> are separate and unrelated types. The void* must be converted back to the original pointer type it was created from.

    So, convert the weak_ptr<something> to weak_ptr<void> first, then convert that to void*, then you can convert the void* back to weak_ptr<void>.


    On a separate note: since you are using placement-new to create the weak_ptr in a memory block allocated by Lua, make sure you destruct the weak_ptr before Lua frees the allocated memory. Which means you have to know the original weak_ptr type that you created so you can call its destructor correctly. Calling reset() on a weak_ptr releases the reference that the weak_ptr holds to another object. It does not destroy the weak_ptr itself.