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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With