问题 C ++ 0x lambda wrappers与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 ); }

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

最后我应该注意到,出于这个问题的目的,some_method是一个很大的功能,可以做很多事情,直接复制到lambda体内会很痛苦。

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

编辑:一个非平凡的用例。

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

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


10531
2017-07-29 02:54


起源

说什么是错的 some_method(a,b,c) 在构造函数/方法中? (顺便说一句,小心你在构造函数中做的事情。) - Kerrek SB


答案:


就可读性和风格而言,我认为实际上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::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中,两个测试都以相同的方式内联。

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


9
2018-01-16 18:17



只是注意,std :: placeholder具有的参数数量是实现定义的。 - OmnipotentEntity


我也不喜欢“使用命名空间;”但后来呢   使绑定表达式不必要地冗长

看来这是你使用的问题 std::bind。您可以使用以下简单的技巧来克服它。

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

演示


2
2017-07-29 03:33





一些更新的建议我发现有用: IStephan T. Lavavej建议不要绑定“避免使用bind(),使用lambdas”。 https://www.youtube.com/watch?v=zt7ThwVfap0&t=32m20s 非常好。

示例,用于在lambda函数中包装成员函数,并发送到第二个矩阵对象的成员函数以应用该函数。

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

所以回顾一下:

  1. 写lambda并捕获[this]
  2. 传递lambda参数和返回值应该与我们希望传递的函数相同
  3. 将lambda传递给std :: function参数...

0
2017-07-16 08:18