在C++中用sol2嵌入Lua时shared_ptr传参异常问题
问题重现
在C++项目中使用sol2嵌入Lua开发,SceneNode类的addChild函数接收std::shared_ptr<SceneNode>参数,但Lua脚本调用时抛出类型不匹配异常,核心错误提示:
sol: 运行时错误: 栈索引2,期望userdata,接收到sol.SceneNode: 值是userdata但不是正确的唯一usertype(参数传入'std::shared_ptr
(std::shared_ptr )'失败)
相关代码
C++ addChild函数
std::shared_ptr<SceneNode> SceneNode::addChild(std::shared_ptr<SceneNode> child) { child->setParent(std::weak_ptr(shared_from_this())); children.push_back(child); return child; }
Lua环境初始化代码
void setup_lua() { sol::state lua; lua.open_libraries(sol::lib::base); lua.new_usertype<SceneNode>("SceneNode", sol::constructors< void(), void(std::string)>(), //sol::base_classes, sol::bases<std::enable_shared_from_this<SceneNode>>(), "id", &SceneNode::id, "name", &SceneNode::name, "addChild", &SceneNode::addChild ); lua.script_file("/Path/to/setup_lua.lua"); }
Lua脚本代码
local node = SceneNode:new("node") print(node.name) print(node.id) local childNode = SceneNode:new("child") node:addChild(childNode) print(childNode.name) print(childNode.id) print(node) print(childNode)
完整报错信息
node -2472761803519894154 [sol2] 发生错误并已传递给错误处理程序: sol: 运行时错误: 栈索引2,期望userdata,接收到sol.SceneNode: 值是userdata但不是正确的唯一usertype(参数传入'std::shared_ptr<SceneNode>(std::shared_ptr<SceneNode>)'失败) stack traceback: [C]: in method 'addChild' /Path/to/setup_lua.lua:13: in main chunk terminate called after throwing an instance of 'sol::error' what(): sol: 运行时错误: 栈索引2,期望userdata,接收到sol.SceneNode: 值是userdata但不是正确的唯一usertype(参数传入'std::shared_ptr<SceneNode>(std::shared_ptr<SceneNode>)'失败) stack traceback: [C]: in method 'addChild' /Path/to/setup_lua.lua:13: in main chunk Aborted (core dumped)
原因分析
sol2默认会将SceneNode作为值类型注册到Lua中,Lua中创建的SceneNode实例是直接存储的原始对象,而非std::shared_ptr<SceneNode>包装的对象。但addChild函数期望接收std::shared_ptr<SceneNode>类型参数,两者类型不匹配导致转换失败。
另外你注释掉了sol::base_classes相关代码,而SceneNode继承了std::enable_shared_from_this<SceneNode>,sol2需要识别这个基类才能正确处理shared_ptr的自动转换。
解决方法
修改Lua环境初始化代码,取消注释sol::base_classes相关行,让sol2识别SceneNode的enable_shared_from_this基类,从而自动用std::shared_ptr管理Lua中的实例:
void setup_lua() { sol::state lua; lua.open_libraries(sol::lib::base); lua.new_usertype<SceneNode>("SceneNode", sol::constructors< void(), void(std::string)>(), sol::base_classes, sol::bases<std::enable_shared_from_this<SceneNode>>(), "id", &SceneNode::id, "name", &SceneNode::name, "addChild", &SceneNode::addChild ); lua.script_file("/Path/to/setup_lua.lua"); }
如果不想依赖enable_shared_from_this,也可以直接注册std::shared_ptr<SceneNode>作为Lua中的usertype,手动绑定成员:
lua.new_usertype<std::shared_ptr<SceneNode>>("SceneNode", sol::constructors<std::shared_ptr<SceneNode>(), std::shared_ptr<SceneNode>(std::string)>(), "id", [](const std::shared_ptr<SceneNode>& self) { return self->id; }, "name", [](const std::shared_ptr<SceneNode>& self) { return self->name; }, "addChild", [](std::shared_ptr<SceneNode>& self, std::shared_ptr<SceneNode> child) { return self->addChild(child); } );
第一种方法更简洁,因为SceneNode已经继承enable_shared_from_this,sol2会自动处理shared_ptr的创建与转换,Lua中通过SceneNode:new()创建的实例会被自动包装为std::shared_ptr,调用addChild时就能正确匹配参数类型。
内容的提问来源于stack exchange,提问作者Yaene

