2011-07-29 52 views
11

Về cơ bản đây là câu hỏi về khả năng đọc, kiểu, hiệu suất của 2 cách tiếp cận khác nhau để tạo/truyền một hàm tử trỏ đến một phương thức thành viên từ bên trong một hàm tạo lớp/phương pháp.C++ 0x lambda wrappers so với liên kết để chuyển các hàm thành viên

Tiếp cận 1:

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

Phương pháp 2:

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

tôi đã tự hỏi nếu sử dụng lambda chỉ là vu vơ trong tình huống này, trong một số khía cạnh đó là dễ dàng hơn để xem những gì đang xảy ra , nhưng sau đó bạn phải cung cấp rõ ràng các loại arg. Ngoài ra tôi không muốn có "sử dụng không gian tên bất cứ điều gì;" nhưng sau đó nó làm cho biểu thức ràng buộc không cần thiết tiết lộ (ví dụ: _1 trở thành std :: placeholders :: _ 1), và lambda tránh được vấn đề này.

Cuối cùng tôi cần lưu ý rằng với mục đích của câu hỏi này, some_method là một hàm lớn thực hiện rất nhiều thứ, và sẽ là một nỗi đau để trực tiếp sao chép vào cơ thể lambda.

Nếu câu hỏi này có vẻ quá mơ hồ, thì chúng tôi có thể tập trung vào câu trả lời cho sự khác biệt về hiệu suất, nếu có.

CHỈNH SỬA: Trường hợp sử dụng không tầm thường.

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

Như bạn có thể thấy, CALLBACK_FUNCTOR được sử dụng trong một danh sách initializer (được định nghĩa với cách tiếp cận 1 hoặc 2) gây khó khăn cho phạm vi một tuyên bố sử dụng (afaik), và rõ ràng là chúng tôi wouldnt bận tâm gói một phương pháp thành viên mà chúng ta có ý định gọi ngay lập tức.

+0

Có gì sai khi chỉ nói 'some_method (a, b, c)' trong hàm tạo/phương thức? (Bằng cách này, hãy cẩn thận với những gì bạn đang làm trong hàm tạo.) –

Trả lời

9

Theo như khả năng đọc và kiểu có liên quan, tôi nghĩ std :: bind trông sạch hơn cho mục đích này, thực sự. std :: placeholders không có gì khác ngoài _ [1-29] để sử dụng với std :: bind như xa như tôi biết, vì vậy tôi nghĩ rằng nó là tốt để chỉ sử dụng "using namespace std :: placeholders;"

Đối với hiệu suất, tôi đã cố gắng tháo một số chức năng kiểm tra:

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

Khi foo() đã không quy định tại các đơn vị dịch tương tự, các kết quả đầu ra lắp ráp là nhiều hơn hoặc ít hơn như nhau cho cả test_lambda và 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 

Tuy nhiên, khi cơ thể của foo được đưa vào các đơn vị dịch tương tự, chỉ có lambda có nội dung của nó inlined (bởi 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::[email protected]@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() [email protected]> 
    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::[email protected]@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() [email protected]> 
    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::[email protected]@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() [email protected]> 
    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 

Ngoài sự tò mò, tôi đã làm lại bài kiểm tra bằng cách sử dụng GCC 4.7 và thấy rằng với 4.7, cả hai bài kiểm tra đều được inlined theo cách tương tự.

Kết luận của tôi là hiệu suất phải giống nhau trong cả hai trường hợp, nhưng bạn có thể muốn kiểm tra đầu ra trình biên dịch của mình nếu nó quan trọng.

+1

Chỉ cần một lưu ý, số đối số mà std :: placeholder đã được thực hiện xác định. – OmnipotentEntity

2

Ngoài ra, tôi không muốn "sử dụng không gian tên bất cứ điều gì"; nhưng sau đó nó làm cho biểu thức ràng buộc không cần thiết tiết lộ chi tiết

Có vẻ như đây là vấn đề của bạn khi sử dụng std::bind. Bạn có thể sử dụng thủ thuật đơn giản dưới đây để vượt qua nó.

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

Demo.

0

Một số tư vấn được cập nhật tôi thấy hữu ích: IStephan T. Lavavej khuyên không ràng buộc "Tránh sử dụng bind(), sử dụng lambdas". https://www.youtube.com/watch?v=zt7ThwVfap0&t=32m20s là khá tốt.

ví dụ về cách gói hàm thành viên trong hàm lambda và gửi tới hàm thành viên của đối tượng ma trận thứ 2 để áp dụng với hàm đó.

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

như vậy để recap:

  1. ghi lambda và chụp [này]
  2. luận qua lambda và giá trị trả về sẽ là tương tự như chức năng chúng tôi muốn vượt qua
  3. qua lambda đến std :: đối số hàm ...
Các vấn đề liên quan