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());
It is UB to convert a given pointer to a
void*and then convert thevoid*to a different pointer type.weak_ptr<something>andweak_ptr<void>are separate and unrelated types. Thevoid*must be converted back to the original pointer type it was created from.So, convert the
weak_ptr<something>toweak_ptr<void>first, then convert that tovoid*, then you can convert thevoid*back toweak_ptr<void>.On a separate note: since you are using
placement-newto create theweak_ptrin a memory block allocated by Lua, make sure you destruct theweak_ptrbefore Lua frees the allocated memory. Which means you have to know the originalweak_ptrtype that you created so you can call its destructor correctly. Callingreset()on aweak_ptrreleases the reference that theweak_ptrholds to another object. It does not destroy theweak_ptritself.