gpt4 book ai didi

lambda - 用于传递成员函数的 C++0x lambda 包装器与绑定(bind)

转载 作者:行者123 更新时间:2023-12-04 02:08:23 25 4
gpt4 key购买 nike

这基本上是一个关于在类构造函数/方法中创建/传递指向成员方法的仿函数的两种不同方法的可读性、样式和性能的问题。

方法一:

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

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

我想知道在这种情况下使用 lambda 是否只是无偿的,在某些方面更容易看到发生了什么,但是你必须显式地提供 arg 类型。我也不喜欢“使用命名空间”;但随后它使绑定(bind)表达式不必要地冗长(例如,_1 变为 std::placeholders::_1),并且 lambda 避免了这个问题。

最后我应该注意,就这个问题而言, some_method 是一个可以做很多事情的大函数,直接复制到 lambda 体中会很痛苦。

如果这个问题看起来太模糊,那么我们可以专注于性能差异的答案(如果有的话)。

编辑:一个不平凡的用例。
MyClass::MyClass()
: some_member_( CALLBACK_FUNCTOR )
{}

如您所见,初始化器列表(使用方法 1 或 2 定义)中使用的 CALLBACK_FUNCTOR 使得限定 using 声明(afaik)的范围很困难,显然我们不会费心包装我们打算立即调用的成员方法。

最佳答案

就可读性和风格而言,我认为 std::bind 实际上看起来更干净。据我所知,std::placeholders 除了 _[1-29] 之外没有任何东西可以与 std::bind 一起使用,所以我认为只使用“使用命名空间 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::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

出于好奇,我使用 GCC 4.7 重新进行了测试,发现在 4.7 中,两个测试都以相同的方式内联。

我的结论是两种情况下的性能应该是相同的,但是如果重要的话,你可能想检查你的编译器输出。

关于lambda - 用于传递成员函数的 C++0x lambda 包装器与绑定(bind),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6868171/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com