2011-01-31 19 views
5

A continuación tengo un concepto de función de miembro de clase vinculante para una función global. El objetivo principal de esto es utilizar C++ para la implementación de la función de devolución de llamada de estilo C. ¿Se puede hacer esto de una mejor manera (por ejemplo, sin macro final o typeof, o usando las características de C++ 0x)?Función de miembro de clase de enlace a función c

#include <iostream> 

using namespace std; 

template<typename MF> struct mf_wrapper_helper; 

template<typename R, typename T> 
struct mf_wrapper_helper<R (T::*)()> 
{ 
    template <R (T::*F)()> 
    static R wrapper(T *foo) { return (foo->*F)(); } 
}; 

template<typename R, typename T, typename T1> 
struct mf_wrapper_helper<R (T::*)(T1)> 
{ 
    template <R (T::*F)(T1)> 
    static R wrapper(T *foo, T1 arg1) { return (foo->*F)(arg1); } 
}; 

#define BIND_MF(mf) \ 
    mf_wrapper_helper<typeof(mf)>::wrapper<mf> 


struct Foo 
{ 
    void x() { cout << "Foo::x()" << endl; } 
    void x1(int i) { cout << "Foo::x1(" << i << ")" << endl; } 
}; 

int 
main() 
{ 
    typedef void (*f_t)(Foo *); 
    typedef void (*f1_t)(Foo *, int i); 

    Foo foo; 

    f_t f_p = BIND_MF(&Foo::x); 
    (*f_p)(&foo); 

    f1_t f1_p = BIND_MF(&Foo::x1); 
    (*f1_p)(&foo, 314); 

    return 0; 
} 
+0

Qué compilador está usando? Si puede usar C++ 0x, solo use lambda's que no tienen captura, se pueden convertir a punteros a funciones. Además, ¿por qué asignar dinámicamente algo en 'main'? – GManNickG

+0

Ignore que 'nuevo' - no importa para la pregunta. Yo uso GCC 4.5 e ICC 11.1. No estoy seguro de cómo ayuda lambda aquí, ya que 'las funciones de Lambda son objetos de función de un tipo dependiente de la implementación'. En realidad, no conozco bien C++ 0x, se agradece el ejemplo del código. – klimkin

+0

Se votó cerrar porque esta pregunta ** probablemente ** sería más adecuada para http://codereview.stackexchange.com/ –

Respuesta

1

creo que la única técnica que funcione a la perfección es escribir una función C envoltorio para cada función miembro que desea invocar dentro de una devolución de llamada C; es decir .:

extern "C" void Foo_x(Foo *foo) 
{ 
    foo->x(); 
} 

extern "C" void Foo_x1(Foo *foo, int i) 
{ 
    foo->x1(i); 
} 

También es posible usar expresiones lambda de C++ 0x, que implícitamente convertir a un puntero a funcionar con el mismo parámetro y tipos de regreso si lo operador de llamada de función del tipo de cierre. Sin embargo, tenga en cuenta que el enlace de idioma del tipo de función es "C++", no "C".

#include <cstdlib> 
#include <iostream> 

using namespace std; 

struct Foo 
{ 
    void x() { cout << "Foo::x()" << endl; } 
    void x1(int i) { cout << "Foo::x1(" << i << ")" << endl; } 
}; 

int main() 
{ 
    typedef void (*f_t)(Foo*); // default ("C++") language linkage 
    typedef void (*f1_t)(Foo*, int); 

    Foo foo; 

    Foo_x(&foo); 
    Foo_x1(&foo, -10); 

    f_t fn = [] (Foo *foo) { 
     foo->x(); 
    }; 
    fn(&foo); 

    f1_t fn1 = [] (Foo *foo, int i) { 
     foo->x1(i); 
    }; 
    fn1(&foo, 314); 

    return EXIT_SUCCESS; 
} 

Tenga en cuenta que la Sección 5.2.2, la llamada de función, del estándar de C++ afirma:

Llamar a una función a través de una expresión cuyo tipo de función tiene una vinculación idioma que es diferente de la lengua de vinculación el tipo de función de la definición de la función llamada no está definida.

Así que la siguiente invoca técnicamente comportamiento indefinido:

extern "C" typedef void (*f_t)(Foo*); 

int main() 
{ 
    Foo foo; 

    f_t fn = [] (Foo *foo) { 
     foo->x(); 
    }; 
    fn(&foo); // `fn` is a pointer to a function that uses "C++" language linkage, 
      // but it is erroneously called through "C" language linkage. 

//... 

EDIT: Después de un poco de experimentación, se me ocurrió con las siguientes funciones de plantilla que devuelven lambdas que llaman a la función miembro determinado:

template <typename return_t, class base, typename... arg_types> 
std::function<return_t (base*, arg_types...)> make_lambda_to_call_member_function(return_t (base::*mem_fn)(arg_types...)) 
{ 
    return [mem_fn] (base *o, arg_types... args) -> return_t { 
     (o->*mem_fn)(args...); 
    }; 
} 

template <typename return_t, class base, typename... arg_types> 
std::function<return_t (const base*, arg_types...)> make_lambda_to_call_member_function(return_t (base::*cmem_fn)(arg_types...) const) 
{ 
    return [cmem_fn] (const base *o, arg_types... args) -> return_t { 
     (o->*cmem_fn)(args...); 
    }; 
} 

Si Foo se define como:

struct Foo 
{ 
    void x() { cout << "Foo::x()" << endl; } 
    void x1(int i) { cout << "Foo::x1(" << i << ")" << endl; } 
    void cx1(float f) const { cout << "Foo::cx1(" << f << ")" << endl; } 
}; 

A continuación, se utiliza la plantilla make_lambda_to_call_member_function como:

auto fn = make_lambda_to_call_member_function(&Foo::x); 
fn(&foo); 

auto fn1 = make_lambda_to_call_member_function(&Foo::x1); 
fn1(&foo, 314); 

auto fn2 = make_lambda_to_call_member_function(&Foo::cx1); 
fn2(&foo, 44.832f); 

Tenga en cuenta, sin embargo, que los objetos devueltos lambda no convertir implícitamente a un puntero de función debido a que la expresión lambda utiliza un lambda-captura.

El último borrador de C++ 0x, N3225, afirma:

El tipo de cierre para un lambda-expresión sin lambda-captura tiene un public const no virtual no explícita función de conversión a puntero a función que tiene el mismo parámetro y tipos de retorno que el operador de llamada de función del tipo de cierre. El valor devuelto por esta función de conversión será la dirección de una función que, cuando se invoca, tiene el mismo efecto que invocar al operador de llamada de función del tipo de cierre.

El siguiente es ilegal:

void (*fn5)(Foo*) = make_lambda_to_call_member_function(&Foo::x); 
+0

Es un buen punto sobre el tipo de enlace. Veo un problema con el uso de lambda: todos los tipos de argumentos se deben enumerar por completo. Con la versión de la plantilla, todos los tipos se calculan con el compilador. ¿Es posible crear una plantilla de función lambda, que tomará la función miembro como parámetro? – klimkin

+0

@klimkin: Actualicé mi respuesta con mucha más información. Para responder a su pregunta, sí, es posible escribir una función de plantilla que devuelva un objeto lambda que invoca una función de miembro determinada, pero ese objeto lambda ya no se puede convertir a un puntero de función. –

Cuestiones relacionadas