С++ 0x lambda wrappers vs. bind для передачи функций-членов

В основном это вопрос о читабельности, стиле, производительности двух разных подходов к созданию/передаче функтора, который указывает на метод-член из конструктора/метода класса.

Подход 1:

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

Подход 2:

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

Мне было интересно, просто ли использовать лямбду в этой ситуации, в некоторых отношениях легче видеть, что происходит, но тогда вам нужно явно указать типы arg. Также я предпочитаю не иметь "использование пространства имен вообще"; но затем делает выражение привязки ненужным подробным (например, _1 становится std:: placeholders:: _ 1), и lambda избегает этой проблемы.

Наконец, я должен отметить, что для целей этого вопроса some_method - большая функция, которая делает много вещей, и было бы болью, чтобы непосредственно скопировать в тело лямбда.

Если этот вопрос кажется слишком расплывчатым, тогда мы можем сосредоточиться на ответах на различия в производительности, если они есть.

EDIT: нетривиальный прецедент.

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

Как вы можете видеть, CALLBACK_FUNCTOR, используемый в списке инициализаторов (определенный с помощью подхода 1 или 2), затрудняет область применения декларации использования (afaik), и, очевидно, мы не будем беспокоиться о переносе метода-члена, который мы намеревались называть прямым прочь.

Ответ 1

Что касается читаемости и стиля, я думаю, что std:: bind выглядит более чистым для этой цели. std:: placeholders не имеет ничего, кроме _ [1-29] для использования с std:: bind, насколько я знаю, поэтому я считаю, что просто использовать "using namespace std:: placeholders;"

Что касается производительности, я попытался разобрать некоторые тестовые функции:

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

Если foo() не было определено в одной и той же системе перевода, выходные данные сборки были более или менее одинаковыми как для test_lambda, так и для 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

Однако, когда тело foo было включено в одну и ту же единицу перевода, только lambda содержала свое содержание (по 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

Из любопытства я переделал тест с помощью GCC 4.7 и обнаружил, что с 4.7 оба теста были встроены таким же образом.

Мое заключение заключается в том, что производительность должна быть одинаковой в любом случае, но вам может потребоваться проверить выход вашего компилятора, если это имеет значение.

Ответ 2

Также я предпочитаю не иметь "использование пространства имен вообще"; но тогда это делает выражение привязки ненужным подробным

Кажется, что это ваша проблема с использованием std::bind. Вы можете использовать ниже простой трюк, чтобы преодолеть его.

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

Демо.

Ответ 3

Некоторые обновленные рекомендации я нашел полезными: IStephan T. Lavavej советует не привязывать "Избегайте использования bind(), используйте lambdas". https://www.youtube.com/watch?v=zt7ThwVfap0&t=32m20s довольно хорошо.

пример для обертывания функции-члена в лямбда-функции и отправки функции-члена второй матрицы-матрицы для применения к этой функции.

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

чтобы повторить:

  • написать лямбда и захватить [this]
  • передать аргументы лямбда, а возвращаемое значение должно быть таким же, как функция, которую мы хотим передать
  • передать lambda в аргумент std:: function...