2011-07-29 20 views
11

Esto es básicamente una pregunta sobre la legibilidad, el estilo, el rendimiento de 2 enfoques diferentes para crear/pasar un funtor que apunta a un método miembro desde dentro de un constructor de clase/método.C++ 0x envolturas lambda vs. enlace para pasar funciones miembro

Enfoque 1:

using namespace std::placeholders; 
std::bind(&MyClass::some_method, this, _1, _2, _3) 

Enfoque 2:

[ this ](const arg1& a, arg2 b, arg3& c) -> blah { some_method(a, b, c); } 

Me preguntaba si se utiliza el lambda es gratuita en esta situación, en algunos aspectos, es más fácil ver lo que está pasando , pero luego debes proporcionar explícitamente los tipos arg. También prefiero no tener "usar el espacio de nombres como sea"; pero luego hace que la expresión de enlace sea innecesariamente detallada (por ejemplo, _1 se convierte en std :: placeholders :: _ 1), y lambda evita este problema.

Por último, debo tener en cuenta que, a los fines de esta pregunta, some_method es una gran función que hace muchas cosas, y sería un dolor copiar directamente en un cuerpo lambda.

Si esta pregunta parece demasiado vaga, entonces podemos centrarnos en las respuestas a las diferencias de rendimiento, si corresponde.

EDITAR: Un caso de uso no trivial.

MyClass::MyClass() 
: some_member_(CALLBACK_FUNCTOR) 
{} 

Como se puede ver, el CALLBACK_FUNCTOR utilizado en una lista de inicialización (definida mediante enfoque 1 o 2) hace que sea difícil alcance una declaración using (que yo sepa), y, obviamente, nos podría molestamos envolver un método miembro de que destinado a llamar de inmediato.

+0

¿Qué tiene de malo simplemente decir 'some_method (a, b, c)' en el constructor/método? (Por cierto, cuidado con lo que estás haciendo en el constructor.) –

Respuesta

9

En lo que respecta a la legibilidad y el estilo, creo que std :: bind parece más limpio para este propósito, en realidad. std :: placeholders no tiene nada más que _ [1-29] para usar con std :: bind hasta donde yo sé, así que creo que está bien solo usar "using namespace std :: placeholders;"

En cuanto a rendimiento, he intentado desmontar algunas funciones de prueba:

#include <functional> 

void foo (int, int, int); 

template <typename T> 
void test_functor (const T &functor) 
{ 
    functor (1, 2, 3); 
} 

template <typename T> 
void test_functor_2 (const T &functor) 
{ 
    functor (2, 3); 
} 

void test_lambda() 
{ 
    test_functor ([] (int a, int b, int c) {foo (a, b, c);}); 
} 

void test_bind() 
{ 
    using namespace std::placeholders; 
    test_functor (std::bind (&foo, _1, _2, _3)); 
} 

void test_lambda (int a) 
{ 
    test_functor_2 ([=] (int b, int c) {foo (a, b, c);}); 
} 

void test_bind (int a) 
{ 
    using namespace std::placeholders; 
    test_functor_2 (std::bind (&foo, a, _1, _2)); 
} 

Cuando foo() no se define de la misma unidad de traducción, las salidas de montaje eran más o menos el mismo para ambos test_lambda y test_bind:

00000000004004d0 <test_lambda()>: 
    4004d0: ba 03 00 00 00   mov $0x3,%edx 
    4004d5: be 02 00 00 00   mov $0x2,%esi 
    4004da: bf 01 00 00 00   mov $0x1,%edi 
    4004df: e9 dc ff ff ff   jmpq 4004c0 <foo(int, int, int)> 
    4004e4: 66 66 66 2e 0f 1f 84 data32 data32 nopw %cs:0x0(%rax,%rax,1) 
    4004eb: 00 00 00 00 00 

00000000004004f0 <test_bind()>: 
    4004f0: ba 03 00 00 00   mov $0x3,%edx 
    4004f5: be 02 00 00 00   mov $0x2,%esi 
    4004fa: bf 01 00 00 00   mov $0x1,%edi 
    4004ff: e9 bc ff ff ff   jmpq 4004c0 <foo(int, int, int)> 
    400504: 66 66 66 2e 0f 1f 84 data32 data32 nopw %cs:0x0(%rax,%rax,1) 
    40050b: 00 00 00 00 00 

0000000000400510 <test_lambda(int)>: 
    400510: ba 03 00 00 00   mov $0x3,%edx 
    400515: be 02 00 00 00   mov $0x2,%esi 
    40051a: e9 a1 ff ff ff   jmpq 4004c0 <foo(int, int, int)> 
    40051f: 90      nop 

0000000000400520 <test_bind(int)>: 
    400520: ba 03 00 00 00   mov $0x3,%edx 
    400525: be 02 00 00 00   mov $0x2,%esi 
    40052a: e9 91 ff ff ff   jmpq 4004c0 <foo(int, int, int)> 
    40052f: 90      nop 

Sin embargo, cuando el cuerpo de foo se incluyó en la misma unidad de traducción, sólo el lambda tenía su contenido inline (por GCC 4.6):

00000000004008c0 <foo(int, int, int)>: 
    4008c0: 53      push %rbx 
    4008c1: ba 04 00 00 00   mov $0x4,%edx 
    4008c6: be 2c 0b 40 00   mov $0x400b2c,%esi 
    4008cb: bf 60 10 60 00   mov $0x601060,%edi 
    4008d0: e8 9b fe ff ff   callq 400770 <std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)@plt> 
    4008d5: 48 8b 05 84 07 20 00 mov 0x200784(%rip),%rax  # 601060 <std::[email protected]@GLIBCXX_3.4> 
    4008dc: 48 8b 40 e8    mov -0x18(%rax),%rax 
    4008e0: 48 8b 98 50 11 60 00 mov 0x601150(%rax),%rbx 
    4008e7: 48 85 db    test %rbx,%rbx 
    4008ea: 74 3c     je  400928 <foo(int, int, int)+0x68> 
    4008ec: 80 7b 38 00    cmpb $0x0,0x38(%rbx) 
    4008f0: 74 1e     je  400910 <foo(int, int, int)+0x50> 
    4008f2: 0f b6 43 43    movzbl 0x43(%rbx),%eax 
    4008f6: bf 60 10 60 00   mov $0x601060,%edi 
    4008fb: 0f be f0    movsbl %al,%esi 
    4008fe: e8 8d fe ff ff   callq 400790 <std::basic_ostream<char, std::char_traits<char> >::put(char)@plt> 
    400903: 5b      pop %rbx 
    400904: 48 89 c7    mov %rax,%rdi 
    400907: e9 74 fe ff ff   jmpq 400780 <std::basic_ostream<char, std::char_traits<char> >::flush()@plt> 
    40090c: 0f 1f 40 00    nopl 0x0(%rax) 
    400910: 48 89 df    mov %rbx,%rdi 
    400913: e8 08 fe ff ff   callq 400720 <std::ctype<char>::_M_widen_init() [email protected]> 
    400918: 48 8b 03    mov (%rbx),%rax 
    40091b: be 0a 00 00 00   mov $0xa,%esi 
    400920: 48 89 df    mov %rbx,%rdi 
    400923: ff 50 30    callq *0x30(%rax) 
    400926: eb ce     jmp 4008f6 <foo(int, int, int)+0x36> 
    400928: e8 e3 fd ff ff   callq 400710 <std::__throw_bad_cast()@plt> 
    40092d: 0f 1f 00    nopl (%rax) 

0000000000400930 <test_lambda()>: 
    400930: 53      push %rbx 
    400931: ba 04 00 00 00   mov $0x4,%edx 
    400936: be 2c 0b 40 00   mov $0x400b2c,%esi 
    40093b: bf 60 10 60 00   mov $0x601060,%edi 
    400940: e8 2b fe ff ff   callq 400770 <std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)@plt> 
    400945: 48 8b 05 14 07 20 00 mov 0x200714(%rip),%rax  # 601060 <std::[email protected]@GLIBCXX_3.4> 
    40094c: 48 8b 40 e8    mov -0x18(%rax),%rax 
    400950: 48 8b 98 50 11 60 00 mov 0x601150(%rax),%rbx 
    400957: 48 85 db    test %rbx,%rbx 
    40095a: 74 3c     je  400998 <test_lambda()+0x68> 
    40095c: 80 7b 38 00    cmpb $0x0,0x38(%rbx) 
    400960: 74 1e     je  400980 <test_lambda()+0x50> 
    400962: 0f b6 43 43    movzbl 0x43(%rbx),%eax 
    400966: bf 60 10 60 00   mov $0x601060,%edi 
    40096b: 0f be f0    movsbl %al,%esi 
    40096e: e8 1d fe ff ff   callq 400790 <std::basic_ostream<char, std::char_traits<char> >::put(char)@plt> 
    400973: 5b      pop %rbx 
    400974: 48 89 c7    mov %rax,%rdi 
    400977: e9 04 fe ff ff   jmpq 400780 <std::basic_ostream<char, std::char_traits<char> >::flush()@plt> 
    40097c: 0f 1f 40 00    nopl 0x0(%rax) 
    400980: 48 89 df    mov %rbx,%rdi 
    400983: e8 98 fd ff ff   callq 400720 <std::ctype<char>::_M_widen_init() [email protected]> 
    400988: 48 8b 03    mov (%rbx),%rax 
    40098b: be 0a 00 00 00   mov $0xa,%esi 
    400990: 48 89 df    mov %rbx,%rdi 
    400993: ff 50 30    callq *0x30(%rax) 
    400996: eb ce     jmp 400966 <test_lambda()+0x36> 
    400998: e8 73 fd ff ff   callq 400710 <std::__throw_bad_cast()@plt> 
    40099d: 0f 1f 00    nopl (%rax) 

00000000004009a0 <test_bind()>: 
    4009a0: ba 03 00 00 00   mov $0x3,%edx 
    4009a5: be 02 00 00 00   mov $0x2,%esi 
    4009aa: bf 01 00 00 00   mov $0x1,%edi 
    4009af: e9 0c ff ff ff   jmpq 4008c0 <foo(int, int, int)> 
    4009b4: 66 66 66 2e 0f 1f 84 data32 data32 nopw %cs:0x0(%rax,%rax,1) 
    4009bb: 00 00 00 00 00 

00000000004009c0 <test_lambda(int)>: 
    4009c0: 53      push %rbx 
    4009c1: ba 04 00 00 00   mov $0x4,%edx 
    4009c6: be 2c 0b 40 00   mov $0x400b2c,%esi 
    4009cb: bf 60 10 60 00   mov $0x601060,%edi 
    4009d0: e8 9b fd ff ff   callq 400770 <std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)@plt> 
    4009d5: 48 8b 05 84 06 20 00 mov 0x200684(%rip),%rax  # 601060 <std::[email protected]@GLIBCXX_3.4> 
    4009dc: 48 8b 40 e8    mov -0x18(%rax),%rax 
    4009e0: 48 8b 98 50 11 60 00 mov 0x601150(%rax),%rbx 
    4009e7: 48 85 db    test %rbx,%rbx 
    4009ea: 74 3c     je  400a28 <test_lambda(int)+0x68> 
    4009ec: 80 7b 38 00    cmpb $0x0,0x38(%rbx) 
    4009f0: 74 1e     je  400a10 <test_lambda(int)+0x50> 
    4009f2: 0f b6 43 43    movzbl 0x43(%rbx),%eax 
    4009f6: bf 60 10 60 00   mov $0x601060,%edi 
    4009fb: 0f be f0    movsbl %al,%esi 
    4009fe: e8 8d fd ff ff   callq 400790 <std::basic_ostream<char, std::char_traits<char> >::put(char)@plt> 
    400a03: 5b      pop %rbx 
    400a04: 48 89 c7    mov %rax,%rdi 
    400a07: e9 74 fd ff ff   jmpq 400780 <std::basic_ostream<char, std::char_traits<char> >::flush()@plt> 
    400a0c: 0f 1f 40 00    nopl 0x0(%rax) 
    400a10: 48 89 df    mov %rbx,%rdi 
    400a13: e8 08 fd ff ff   callq 400720 <std::ctype<char>::_M_widen_init() [email protected]> 
    400a18: 48 8b 03    mov (%rbx),%rax 
    400a1b: be 0a 00 00 00   mov $0xa,%esi 
    400a20: 48 89 df    mov %rbx,%rdi 
    400a23: ff 50 30    callq *0x30(%rax) 
    400a26: eb ce     jmp 4009f6 <test_lambda(int)+0x36> 
    400a28: e8 e3 fc ff ff   callq 400710 <std::__throw_bad_cast()@plt> 
    400a2d: 0f 1f 00    nopl (%rax) 

0000000000400a30 <test_bind(int)>: 
    400a30: ba 03 00 00 00   mov $0x3,%edx 
    400a35: be 02 00 00 00   mov $0x2,%esi 
    400a3a: e9 81 fe ff ff   jmpq 4008c0 <foo(int, int, int)> 
    400a3f: 90      nop 

Por curiosidad, rehice la prueba usando GCC 4.7, y encontré que con 4.7, ambas pruebas se marcaron de la misma manera.

Mi conclusión es que el rendimiento debería ser el mismo en ambos casos, pero es posible que desee comprobar la salida de su compilador si es importante.

+1

Solo una nota, el número de argumentos que std :: placeholder tiene es la implementación definida. – OmnipotentEntity

2

También prefiero no tener "usar el espacio de nombre lo que sea;" pero luego se hace que la expresión se unen innecesariamente prolijo

Parece que esta es tu problema con el uso std::bind. Puedes usar el truco simple debajo para superarlo.

void myfoo() 
{ 
    //... 
    { 
    using namespace std::placeholders; // scope available only in this block 
    std::bind(&MyClass::some_method, this, _1, _2, _3); 
    } 
//... 
} 

Demo.

0

Algunos consejos actualizados he encontrado útil: IStephan T. Lavavej asesorar sobre no vinculante "Evite el uso de bind(), use lambdas". https://www.youtube.com/watch?v=zt7ThwVfap0&t=32m20s es bastante bueno.

ejemplo para envolver una función de miembro en una función lambda y enviarla a una función miembro de segundo objeto de matriz para aplicar con esa función.

//some member function that does cool math stuff 
float a_class::math_func(float x) 
{ 
    return 1.0f; 
} 

// another member function 
void a_class::some_func2() 
{ 
    //create spot matrix of window size 
    util_mat::mat<float> spot(window_size_, window_size_); 

    auto f_callback = [this](float n) { return math_func(n); }; 

    //apply math callback function on spot matrix and get new noise matrix. 
    util_mat::mat<float> noise_mat = spot.apply_func(f_callback); 
} 

float mat<T>::appy_func(std::function<float(float)> func) 
{ 
    //do somthing 
} 

por lo que para recapitular:

  1. lambda escritura y la captura de [esta]
  2. argumentos pase lambda y el valor de retorno debe ser la misma que la función que deseamos pasar lambda
  3. pase a la std :: function argument ...
Cuestiones relacionadas