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 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.