9

我有一个依赖std::shared_ptr很多的 API(一个特定的 GUI 库),即它们通常用作函数参数并存储在其他对象中。例如,容器小部件(如拆分器和框)会将其子小部件存储在shared_ptrs 中。现在我想通过 luabind 将此 API 映射到 Lua。在理想情况下,Luabind 会在 shared_ptr 中创建新对象,并允许我将这些对象直接传递给采用 shared_ptr 参数的函数。这似乎适用于单个课程,例如:

luabind::class_<Button, std::shared_ptr<Button>>("Button")

虽然我这样声明它,但我可以公开和使用void foo(std::shared_ptr<Button> const&).

现在 luabind 手册提到,为了使用类的层次结构,我必须对层次结构中的所有类使用相同的 shared_ptr 模板实例,例如

luabind::class_<BaseWidget, std::shared_ptr<BaseWidget>>("BaseWidget"),
luabind::class_<Button, BaseWidget, std::shared_ptr<BaseWidget>>("Button")

现在我不能再调用foo了——它将无法从 Lua 中找到函数。我能以某种方式让 luabind 仍然支持在shared_ptrs 中传递按钮吗?另外,我想知道为什么 luabind 要求您对层次结构中的所有类使用相同的智能指针,而不是仅仅将它们转换为基类指针。

4

1 回答 1

7

我认为要让它工作,你必须像这样绑定你的派生类:

luabind::class_<Button, BaseWidget, std::shared_ptr<Button>> ("Button")

例如:

class BaseWidget
{
public:
    static void Bind2Lua(lua_State* l)
    {
        luabind::module(l)
        [
            luabind::class_<BaseWidget, std::shared_ptr<BaseWidget>> ("BaseWidget")
            .def(luabind::constructor<>())
        ];
    }

    virtual ~BaseWidget()
    {

    }
};

class Button : public BaseWidget
{
public:
    static void Bind2Lua(lua_State* l)
    {
        luabind::module(l)
        [
            luabind::class_<Button, BaseWidget, std::shared_ptr<Button>> ("Button")
            .def(luabind::constructor<>())
            .def("Click", &Button::Click)
        ];
    }

    void Click()
    {
        std::cout << "Button::Click" << std::endl;
    }
};

现在您可以将它与 shared_ptr 一起使用:

class Action
{
public:
    void DoClick(const std::shared_ptr<Button>& b)
    {
        // perform click action
        b->Click();
    }

    static void Bind2Lua(lua_State* l)
    {
        luabind::module(l)
        [
            luabind::class_<Action> ("Action")
            .def(luabind::constructor<>())
            .def("DoClick", &Action::DoClick)
        ];
    }
};

在 lua 中:

b = Button()

a = Action()

a:DoClick(b)

原因是 luabind 使用带整数的类型 ID 系统(更准确地说,std::size_t 在inheritance.hpp 中定义)。
您可以使用以下函数获取任何已注册类型的类型 ID:

luabind::detail::static_class_id<T>(nullptr);

其中 T 是注册类。
在我的演示程序中,它们是:

  • 基本小部件 = 3
  • std::shared_ptr<BaseWidget> = 6
  • 按钮 = 4
  • std::shared_ptr< 按钮 > = 7
  • 行动 = 5

所以当你从lua调用DoClick时,它会调用instance_holder.hpp中模板类pointer_holder的get成员:

std::pair<void*, int> get(class_id target) const
{
    if (target == registered_class<P>::id)
        return std::pair<void*, int>(&this->p, 0);

    void* naked_ptr = const_cast<void*>(static_cast<void const*>(
        weak ? weak : get_pointer(p)));

    if (!naked_ptr)
        return std::pair<void*, int>((void*)0, 0);

    return get_class()->casts().cast(
        naked_ptr
      , static_class_id(false ? get_pointer(p) : 0)
      , target
      , dynamic_id
      , dynamic_ptr
    );
}

如您所见,如果目标类与注册的不同,它将尝试进行强制转换。
这就是事情变得有趣的地方。如果您将 Button 类声明为

luabind::class_<Button, BaseWidget,std::shared_ptr<BaseWidget>>("Button")

然后该实例将作为一个 shared_ptr 保存到 BaseWidget,因此 cast 函数将尝试从 BaseWidget (3) 转换为 std::shared_ptr< Button > (7) 并且失败。如果 luabind 支持 base-to-derived 转换,它可以工作,但它似乎不支持。

但是,如果您将 Button 类声明为

luabind::class_<Button, BaseWidget, std::shared_ptr<Button>> ("Button")

然后该实例将作为一个 shared_ptr 保存到 Button ,然后目标 id 将匹配注册的类型。get 函数将在第一次返回时分支,从不打扰演员表。

您还可以在 pastebin 找到我在这里使用的自包含程序。

这里有一个有趣的断点列表,你可以设置看看发生了什么(luabind 版本 900):

  • instance_holder.hpp 中的第 94 行(pointer_holder::get 的第一行)
  • instance.cpp 中的第 143 行(cast_graph::impl::cast 的第一行)
于 2012-06-27T14:42:36.523 回答