C ++ 0x lambda包装器与用于传递成员函数的bind

时间:2011-07-29 02:54:24

标签: lambda c++11 bind member-function-pointers

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

方法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)的范围变得很困难,显然我们不会打包包含我们打算直接调用的成员方法程。

3 个答案:

答案 0 :(得分:9)

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

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

答案 1 :(得分:2)

  

此外,我更喜欢不使用“使用命名空间;”但后来呢   使绑定表达式不必要地详细

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

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

Demo

答案 2 :(得分:0)

我觉得有用的一些更新建议: 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参数...