Usando luabind y std::shared_ptr con herencia

Tengo una API (una biblioteca GUI específica) que se basa en std::shared_ptr mucho, es decir, a menudo se utilizan como parámetros de función y se almacenan dentro de otros objetos. Por ejemplo, los widgets de contenedor, como divisores y cajas, almacenarán sus widgets secundarios en shared_ptrs. Ahora me gustaría asignar esta API a Lua a través de luabind. En un mundo ideal, Luabind crearía nuevos objetos en shared_ptrs y me permitiría pasarlos directamente a las funciones que toman los parámetros shared_ptr. Esto parece funcionar para clases individuales, por ejemplo:

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

Mientras lo declaro así, puedo exponer y usar funciones como void foo(std::shared_ptr<Button> const&).

Ahora, el manual de luabind menciona que para usar una jerarquía de clases, tendría que usar la misma instancia de plantilla shared_ptr para todas las clases en la jerarquía, por ejemplo

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

Ahora ya no puedo llamar foo - no podrá encontrar la función de Lua. ¿Puedo de alguna manera hacer que luabind siga admitiendo pasar botones en shared_ptr¿s? Además, me gustaría saber por qué luabind exige que use el mismo puntero inteligente para todas las clases en la jerarquía en lugar de que solo se conviertan en punteros de clase base.

preguntado el 03 de mayo de 12 a las 15:05

¿No debería usar esa segunda línea std::shared_ptr<BaseWidget> no std::shared_ptr<Button>? -

¡Sí, gracias! ¡Solo un tipo aunque! -

1 Respuestas

Creo que para que funcione tienes que vincular tu clase derivada de esta manera:

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

Por ejemplo:

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

Ahora puedes usarlo con 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)
        ];
    }
};

En lua:

b = Button()

a = Action()

a:DoClick(b)

La razón es que luabind usa un sistema de identificación de tipo con números enteros (más precisamente, std::size_t como se define en heritage.hpp).
Puede obtener el type-id de cualquier tipo registrado con la función:

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

Donde T es la clase registrada.
En mi programa de demostración son:

  • Widget base = 3
  • std::shared_ptr<BaseWidget> = 6
  • Botón = 4
  • std::shared_ptr<Botón> = 7
  • Acción = 5

Entonces, cuando llame a DoClick desde lua, llamará al miembro get de la clase de plantilla pointer_holder en instance_holder.hpp:

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

Como puede ver, si la clase objetivo no es la misma que la registrada, intentará hacer un lanzamiento.
Aquí es donde las cosas se ponen interesantes. Si declaraste la clase Button como

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

luego, la instancia se mantendrá como shared_ptr en BaseWidget, por lo que la función de conversión intentará convertir desde BaseWidget (3) a std::shared_ptr< Button > (7) y eso falla. Podría funcionar si luabind admitiera la conversión de base a derivada, lo que no parece ser.

Sin embargo, si declaraste la clase Button como

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

luego, la instancia se mantendrá como shared_ptr to Button y luego la identificación de destino coincidirá con el tipo registrado. La función get se bifurcará en el primer retorno, sin molestarse nunca con el cast.

También puede encontrar el programa autónomo que utilicé. aquí en pastebin.

Y aquí hay una lista de puntos de interrupción interesantes que puede configurar para ver qué sucede (luabind versión 900):

  • línea 94 en instance_holder.hpp (primera línea de pointer_holder::get)
  • línea 143 en instancia.cpp (primera línea de cast_graph::impl::cast)

Respondido el 23 de diciembre de 13 a las 16:12

De hecho, agregué esas sobrecargas para get_pointer. Definitivamente pasa el smart_pointer (lo puedo decir porque los recuentos de referencia son correctos si llamo a esas funciones varias veces) - ltjax

Además, pasar el puntero sin procesar es aún más complicado ya que quiero almacenar los punteros, no solo ejecutar funciones en ellos. Para ese trabajo, tendría que modificar la jerarquía de objetos de la GUI para derivar de enable_shared_from_this. - ltjax

Pero con su solución actual, ignora que el manual de luabind exige usar el tipo shared_ptr de clase base: vea rasterbar.com/products/luabind/docs.html#smart-pointers, ultimo parrafo. Si esto "simplemente funciona", me gustaría saber por qué... - ltjax

Creo que es un error cometido por la documentación de luabind. Presentaré un informe de error y detallaré por qué funciona así en mi respuesta. ¡Hice una depuración bastante extensa para resolver esto! - julien lebot

A mi modo de ver, el único problema que podría ocurrir es que luabind intente asignar un puntero sin procesar a otro puntero inteligente, de modo que la instancia quede en manos de dos cuerpos diferentes de recuento de referencias... ¿Podría suceder esto si intente pasar un botón a una función que toma un std::shared_ptr ? - ltjax

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.