C++0x lambda wrappers vs. bind for passing member

2019-04-20 04:26发布

问题:

This is basically a question about the readability, style, performance of 2 different approaches to creating/passing a functor that points to a member method from within a class constructor/method.

Approach 1:

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

Approach 2:

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

I was wondering if using the lambda is just gratuitous in this situation, in some respects it is easier to see what is going on, but then you have to explicitly provide the arg types. Also i prefer not to have "using namespace whatever;" but then it makes the bind expression needlessly verbose (eg. _1 becomes std::placeholders::_1), and lambda avoids this issue.

Finally i should note that for the purposes of this question, some_method is a big function that does lots of things, and would be a pain to directly copy into a lambda body.

If this question seems too vague, then we can focus on answers to the performance differences, if any.

EDIT: A non-trivial use case.

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

As you can see, the CALLBACK_FUNCTOR used in an initializer list (defined with approach 1 or 2) makes it difficult to scope a using declaration (afaik), and obviously we wouldnt bother wrapping a member method that we intended to call straight away.

回答1:

As far as readability and style are concerned, I think std::bind looks cleaner for this purpose, actually. std::placeholders does not have anything other than _[1-29] for use with std::bind as far as I know, so I think it is fine to just use "using namespace std::placeholders;"

As for performance, I tried disassembling some test functions:

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

When foo() was not defined in the same translation unit, the assembly outputs were more or less the same for both test_lambda and 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

However, when the body of foo was included into the same translation unit, only the lambda had its contents inlined (by 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::cout@@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() const@plt>
  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::cout@@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() const@plt>
  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::cout@@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() const@plt>
  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

Out of curiosity, I redid the test using GCC 4.7, and found that with 4.7, both tests were inlined in the same manner.

My conclusion is that the performance should be the same in either case, but you might want to check your compiler output if it matters.



回答2:

Also i prefer not to have "using namespace whatever;" but then it makes the bind expression needlessly verbose

It seems that this is your problem with using std::bind. You can use below simple trick to overcome it.

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

Demo.



回答3:

Some updated advise i found useful: IStephan T. Lavavej advise on not binding "Avoid using bind(), use lambdas". https://www.youtube.com/watch?v=zt7ThwVfap0&t=32m20s is pretty good.

example for a wrapping of a member function in a lambda function and sending to a 2nd matrix object's member function to apply with that function.

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

so to recap:

  1. write lambda and capture [this]
  2. pass lambda arguments and return value should be the same as function we wish to pass
  3. pass lambda to the std::function argument...