2009-08-20 14 views
11

Actualmente estoy usando GCC 4.4, y estoy teniendo bastante dolor de cabeza entre void * y un puntero a la función miembro. Estoy intentando escribir una biblioteca fácil de usar para los objetos C++ unión a un intérprete de Lua, así:Casting entre void * y un puntero a la función de miembro

LuaObject<Foo> lobj = registerObject(L, "foo", fooObject); 
lobj.addField(L, "bar", &Foo::bar); 

Tengo la mayor parte de que se haga, a excepción de la siguiente función (que es específica a una cierta firma de función hasta que tenga la oportunidad de generalizar):

template <class T> 
int call_int_function(lua_State *L) 
{ 
    // this next line is problematic 
    void (T::*method)(int, int) = reinterpret_cast<void (T::*)(int, int)>(lua_touserdata(L, lua_upvalueindex(1))); 
    T *obj = reinterpret_cast<T *>(lua_touserdata(L, 1)); 

    (obj->*method)(lua_tointeger(L, 2), lua_tointeger(L, 3)); 
    return 0; 
} 

para aquellos que no están familiarizados con Lua, lua_touserdata(L, lua_upvalueindex(1)) obtiene el primer valor asociado con un cierre (en este caso, es el puntero a la función miembro) y lo devuelve como un vacío *. GCC se queja de que void * -> void (T :: *) (int, int) es un molde inválido. ¿Alguna idea sobre cómo solucionar esto?

+5

http://www.parashift.com/c++-faq-lite/pointers-to-members .html –

+0

+1 la anterior ... específicamente la sección 33.7 & 33.8 – fbrereto

+1

Solo por curiosidad, ¿por qué intentas almacenar funciones C en los datos de usuario de Lua así? Probablemente haya una forma más segura de lograr su objetivo. – Alex

Respuesta

17

cannot cast a pointer-to-member to void * o cualquier otro tipo de puntero "normal". Los punteros a miembros no abordan la forma en que son los punteros regulares. Lo que probablemente tendrá que hacer es ajustar su función de miembro en una función normal. Las preguntas frecuentes de C++ Lite explains this en algún detalle. El problema principal es que los datos necesarios para implementar un puntero a miembro no son solo una dirección, y de hecho, varies tremendously según la implementación del compilador.

Supongo que tiene control sobre los datos de usuario lua_touserdata que está devolviendo. No puede ser un puntero-a-miembro ya que no hay una forma legal de obtener esta información de nuevo. Pero tiene otras opciones:

  • La opción más simple es probablemente envolver su función de miembro en una función libre y devolverla. Esa función libre debería tomar el objeto como su primer argumento. Vea el ejemplo de código a continuación.

  • Utilice una técnica similar a la de Boost.Bind's mem_fun para devolver un objeto de función, que puede plantilla correctamente. No veo que esto sea más fácil, pero le permitiría asociar el estado más con la función de retorno si lo necesitara.

Aquí es una reescritura de su función con la primera forma:

template <class T> 
int call_int_function(lua_State *L) 
{ 
    void (*method)(T*, int, int) = reinterpret_cast<void (*)(T*, int, int)>(lua_touserdata(L, lua_upvalueindex(1))); 
    T *obj = reinterpret_cast<T *>(lua_touserdata(L, 1)); 

    method(obj, lua_tointeger(L, 2), lua_tointeger(L, 3)); 
    return 0; 
} 
+0

No creo que el puntero a funciones miembro sea diferente de los punteros normales. ¿Qué te hace pensar que tienen propiedades especiales? Simplemente apuntan a un pedazo de código. –

+0

Martin me educaron sobre este punto en SO recientemente. Permítanme señalarles la discusión aquí: http://stackoverflow.com/questions/1207106/calling-base-class-definition-of-virtual-member-function-with-function-pointer/1207396#1207396. – quark

+2

Martin: Lea también el enlace marcado "variado tremendamente": http://www.codeproject.com/KB/cpp/ FastDelegate.aspx. Los punteros a las funciones de miembro no se implementan necesariamente como punteros, ni siquiera de la misma manera de un sistema a otro. Pueden ser combinaciones de una tabla y un índice, completos en thunks o cualquiera de varias implementaciones variantes. – quark

1

Como solución dadas las restricciones de fundición de una función de puntero a miembro de void* que podría envolver el puntero de función en un pequeño montón asignados estructura y poner un puntero a la estructura en su Lua usuario de datos:

template <typename T> 
struct LuaUserData { 
    typename void (T::*MemberProc)(int, int); 

    explicit LuaUserData(MemberProc proc) : 
     mProc(proc) 
    { } 

    MemberProc mProc; 
}; 

LuaObject<Foo> lobj = registerObject(L, "foo", fooObject); 
LuaUserData<Foo>* lobj_data = new LuaUserData<Foo>(&Foo::bar); 

lobj.addField(L, "bar", lobj_data); 

// ... 

template <class T> 
int call_int_function(lua_State *L) 
{ 
    typedef LuaUserData<T>      LuaUserDataType; 
    typedef typename LuaUserDataType::MemberProc ProcType; 

    // this next line is problematic 
    LuaUserDataType* data = 
     reinterpret_cast<LuaUserDataType*>(lua_touserdata(L, lua_upvalueindex(1))); 
    T *obj = reinterpret_cast<T *>(lua_touserdata(L, 1)); 

    (obj->*(data.mMemberProc))(lua_tointeger(L, 2), lua_tointeger(L, 3)); 
    return 0; 
} 

no soy experto con Lua así que probablemente haya pasado por alto algo en el ejemplo anterior. Tenga en cuenta que, si realiza esta ruta, deberá administrar la asignación de LuaUserData.

4

Es posible convertir puntero a funciones miembro y atributos utilizando uniones:

// helper union to cast pointer to member 
template<typename classT, typename memberT> 
union u_ptm_cast { 
    memberT classT::*pmember; 
    void *pvoid; 
}; 

convertir, poner el valor de origen a uno de los miembros, y tire el valor objetivo de la otra.

Si bien este método es práctico, no tengo idea de si va a funcionar en todos los casos.

+2

Creo que los apuntadores de miembros son en realidad objetos grandes; es muy probable que 'sizeof (void *)

+1

He intentado este enfoque con el MSVC November CTP. Agregué un 'static_assert' para asegurar que' sizeof (u_ptm_cast :: pmember) == sizeof (u_ptm_cast :: pvoid) '. Parece _ funcionar en la mayoría de las situaciones, p. cuando pmember no es virtual, virtual o cuando 'classT' se sustituye por una clase diferente cuando se convierte de nuevo a un puntero de función miembro. Sin embargo, si 'classT' emplea herencia múltiple, el puntero es de hecho más grande y la afirmación falla. –

+0

En GCC 4.7, todos los punteros de función de miembro parecen tener el tamaño de dos punteros normales. Teniendo en cuenta esto, haciendo 'pvoid' una matriz del doble del tamaño, la técnica de @ paniq que implica una' unión' funciona para los mismos casos que con el compilador MS. En general, es una solución bastante frágil y poco útil en el problema de ligadura de Lua. –

1

A diferencia de la dirección de un no estático función miembro , que es un tipo de puntero a miembro con una representación complicado, la dirección de una función miembro estático es por lo general un sólo una dirección de máquina, compatible con una conversión a void *.

Si necesita vincular una función de miembro no estática de C++ a un mecanismo de devolución de llamada tipo C o C basado en void *, lo que puede intentar es escribir un contenedor estático en su lugar.

La envoltura puede tener un puntero a una instancia como un argumento, y pasar el control a la función miembro no estática:

void myclass::static_fun(myclass *instance, int arg) 
{ 
    instance->nonstatic_fun(arg); 
} 
+0

He visto este patrón en varias bibliotecas, pero ¿cómo se llena el puntero de instancia en la función de devolución de llamada? ¿Compilador mágico? – andig

+1

@andig La interfaz de devolución de llamada debe tener un argumento de contexto para el paso de los datos de usuario registrados. Es decir. "Por favor, pásame el puntero cuando me vuelvas a llamar". Si la interfaz de devolución de llamada no tiene esto, puede ser pirateado con trampolines. Puede colocar un pequeño código de máquina (el trampolín) en la parte frontal del objeto de devolución de llamada, y ese código de máquina se utiliza como la función de devolución de llamada.Calcula el puntero del objeto relativo a su puntero de instrucción, sabiendo que el objeto está en un desplazamiento fijo desde su propia dirección. Luego llama a la función real. – Kaz

0

En este caso, basta con cambiar los parámetros de la void_cast función para que se adapte a sus necesidades:

template<typename T, typename R> 
void* void_cast(R(T::*f)()) 
{ 
    union 
    { 
     R(T::*pf)(); 
     void* p; 
    }; 
    pf = f; 
    return p; 
} 

ejemplo uso:

auto pvoid = void_cast(&Foo::foo); 
Cuestiones relacionadas