You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将weak_ptr<SomeThing>*转void*再转回weak_ptr<void*>是否合法?能否用统一Lua __gc回调?

问题

我在程序中嵌入了Lua脚本,让Lua管理某些std::shared_ptr和std::weak_ptr的生命周期。具体实现是在Lua管理的内存(userdata)中构造这些指针,当Lua准备对该内存做垃圾回收时,会传入指向该内存的void*,我可以在内存释放前把void*转换回对应的指针并调用reset()方法。

现在的疑问是:能不能给所有类型的std::weak_ptr共用同一个垃圾回收回调(Lua的__gc元方法)?这需要把void*直接static_cast成std::weak_ptr<void>*,但这块内存最初构造的是std::weak_ptr<Something>类型。这种操作合法吗?

原始代码片段

创建userdata并构造weak_ptr:

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);

对应的__gc元方法:

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");

测试显示这段代码能运行,但我想确认它是否属于未定义行为,或者是否依赖具体实现?还是说必须为每个weak_ptr的类型变体单独创建元表?


编辑后更新的代码

基于建议,我把__gc例程改成销毁weak_ptr而非仅调用reset():

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");

存储指针的方式改为:

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);

使用时的代码:

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());

回答

首先明确:原始代码的做法属于未定义行为,不能直接把std::weak_ptr<Something>*通过static_cast转换成std::weak_ptr<void>*来操作。

原因在于,C++标准并未规定不同模板实例化的std::weak_ptr类型之间有布局兼容性。虽然在很多编译器实现中,不同类型的std::weak_ptr可能有着相同的内存布局,但这只是具体实现的巧合,并非标准要求。依赖这种行为的代码,在不同编译器、不同版本的标准库下可能出现崩溃、内存泄漏或其他不可预测的问题。

而你编辑后的代码是合法且安全的,理由如下:

  1. 你统一在userdata中构造std::weak_ptr<void>,而非具体类型的weak_ptr。std::weak_ptr支持从任意类型的std::shared_ptr/std::weak_ptr隐式转换为std::weak_ptr<void>,这是标准允许的操作。
  2. 在__gc回调中,你操作的是真正构造在该内存上的std::weak_ptr<void>对象,使用std::destroy_at正确销毁它,符合对象的生命周期管理要求。
  3. 使用时通过std::static_pointer_cast将std::shared_ptr<void>转换回原类型,这也是标准允许的安全操作,前提是转换的目标类型与原始shared_ptr管理的对象类型一致。

这种统一用std::weak_ptr<void>的方式,完全可以用同一个元表和__gc回调来管理所有类型的weak_ptr,无需为每个类型单独创建元表,既保证了代码的合法性,也简化了实现。


内容的提问来源于stack exchange,提问作者Neil Stephens

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 11:08:14