Câu hỏi Một biểu thức lambda trong C ++ 11 là gì?


Một biểu thức lambda trong C ++ 11 là gì? Khi nào tôi sẽ sử dụng? Họ giải quyết vấn đề gì mà không thể trước khi giới thiệu của họ?

Một vài ví dụ và các trường hợp sử dụng sẽ hữu ích.


1198
2017-10-02 14:58


gốc


Tôi đã nhìn thấy một trường hợp lambda rất hữu ích: Một đồng nghiệp của tôi đang làm mã có hàng triệu lần lặp để giải quyết vấn đề tối ưu hóa không gian. Thuật toán nhanh hơn nhiều khi sử dụng lambda hơn là một hàm thích hợp! Trình biên dịch là Visual C ++ 2013. - sergiol


Các câu trả lời:


Vấn đề

C ++ bao gồm các hàm chung hữu ích như std::for_each và std::transform, có thể rất tiện dụng. Thật không may họ cũng có thể khá cồng kềnh để sử dụng, đặc biệt nếu functor bạn muốn áp dụng là duy nhất cho hàm cụ thể.

#include <algorithm>
#include <vector>

namespace {
  struct f {
    void operator()(int) {
      // do something
    }
  };
}

void func(std::vector<int>& v) {
  f f;
  std::for_each(v.begin(), v.end(), f);
}

Nếu bạn chỉ sử dụng f một lần và ở nơi cụ thể đó có vẻ như quá mức cần thiết để viết một lớp học toàn bộ chỉ để làm một cái gì đó tầm thường và một off.

Trong C ++ 03 bạn có thể bị cám dỗ để viết một cái gì đó như sau, để giữ cho hàm functor cục bộ:

void func2(std::vector<int>& v) {
  struct {
    void operator()(int) {
       // do something
    }
  } f;
  std::for_each(v.begin(), v.end(), f);
}

tuy nhiên điều này không được phép, f không thể được chuyển đến một hàm mẫu trong C ++ 03.

Giải pháp mới

C ++ 11 giới thiệu lambdas cho phép bạn viết một hàm functor nội tuyến, ẩn danh để thay thế struct f. Đối với các ví dụ nhỏ đơn giản, điều này có thể sạch hơn để đọc (nó giữ mọi thứ ở một nơi) và có khả năng đơn giản hơn để duy trì, ví dụ ở dạng đơn giản nhất:

void func3(std::vector<int>& v) {
  std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}

Hàm lambda chỉ là cú pháp cú pháp cho các functors ẩn danh.

Loại trả lại

Trong trường hợp đơn giản, kiểu trả về của lambda được suy ra cho bạn, ví dụ:

void func4(std::vector<double>& v) {
  std::transform(v.begin(), v.end(), v.begin(),
                 [](double d) { return d < 0.00001 ? 0 : d; }
                 );
}

tuy nhiên khi bạn bắt đầu viết các lambdas phức tạp hơn, bạn sẽ nhanh chóng gặp phải các trường hợp mà kiểu trả về không thể được trình biên dịch suy ra, ví dụ:

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

Để giải quyết vấn đề này, bạn được phép chỉ định rõ ràng kiểu trả về cho hàm lambda, sử dụng -> T:

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) -> double {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

Các biến "Đang chụp"

Cho đến nay chúng tôi đã không sử dụng bất cứ điều gì khác hơn những gì đã được truyền cho lambda bên trong nó, nhưng chúng tôi cũng có thể sử dụng các biến khác, trong lambda. Nếu bạn muốn truy cập các biến khác, bạn có thể sử dụng mệnh đề chụp ( [] của biểu thức), cho đến nay vẫn chưa được sử dụng trong các ví dụ này, ví dụ:

void func5(std::vector<double>& v, const double& epsilon) {
    std::transform(v.begin(), v.end(), v.begin(),
        [epsilon](double d) -> double {
            if (d < epsilon) {
                return 0;
            } else {
                return d;
            }
        });
}

Bạn có thể chụp bằng cả tham chiếu và giá trị mà bạn có thể chỉ định bằng cách sử dụng & và = tương ứng:

  • [&epsilon] chụp bằng tham chiếu
  • [&] nắm bắt tất cả các biến được sử dụng trong lambda bằng tham chiếu
  • [=] nắm bắt tất cả các biến được sử dụng trong lambda theo giá trị
  • [&, epsilon] nắm bắt các biến như với [&], nhưng epsilon theo giá trị
  • [=, &epsilon] nắm bắt các biến như với [=], nhưng epsilon theo tham chiếu

Được tạo operator() Là const theo mặc định, với ngụ ý rằng việc chụp sẽ là const khi bạn truy cập chúng theo mặc định. Điều này có hiệu lực mà mỗi cuộc gọi với cùng một đầu vào sẽ tạo ra cùng một kết quả, tuy nhiên bạn có thể đánh dấu lambda là mutable để yêu cầu operator() được sản xuất không const.


1210
2017-10-02 15:21



@Yakk bạn đã bị mắc kẹt. lambdas mà không có một nắm bắt có một chuyển đổi tiềm ẩn để con trỏ loại chức năng. hàm chuyển đổi là const luôn luôn... - Johannes Schaub - litb
@ JohannesSchaub-litb oh lén lút - và nó xảy ra khi bạn gọi () - nó được chuyển thành một lambda không đối số, nhưng vì () const không phù hợp với lambda, nó sẽ tìm kiếm một kiểu chuyển đổi cho phép nó, bao gồm con trỏ trỏ tới hàm, và sau đó gọi nó! Lén lút! - Yakk - Adam Nevraumont
Thú vị - Ban đầu tôi nghĩ rằng lambdas là vô danh chức năng thay vì functors, và đã bị nhầm lẫn về cách chụp ảnh hoạt động. - immibis
Nếu bạn muốn sử dụng lambdas làm biến trong chương trình của mình, bạn có thể sử dụng: std::function<double(int, bool)> f = [](int a, bool b) -> double { ... };  Nhưng thông thường, chúng tôi cho phép trình biên dịch suy ra loại: auto f = [](int a, bool b) -> double { ... };  (và đừng quên #include <functional>) - evertheylen
Tôi cho rằng không phải ai cũng hiểu tại sao return d < 0.00001 ? 0 : d; được đảm bảo trả về gấp đôi, khi một trong các toán hạng là hằng số nguyên (đó là do quy tắc quảng cáo tiềm ẩn của toán tử?: trong đó toán hạng 2 và 3 được cân bằng với nhau thông qua chuyển đổi số học thông thường bất kể chuyển đổi nào được chọn). Thay đổi thành 0.0 : d có lẽ sẽ làm cho ví dụ dễ hiểu hơn. - Lundin


Hàm lambda là gì?

Khái niệm C ++ về hàm lambda bắt nguồn từ phép tính lambda và lập trình hàm. Một lambda là một hàm chưa được đặt tên hữu ích (trong lập trình thực tế, không phải lý thuyết) cho các đoạn mã ngắn không thể tái sử dụng và không đáng đặt tên.

Trong C ++, hàm lambda được định nghĩa như thế này

[]() { } // barebone lambda

hoặc trong tất cả vinh quang của nó

[]() mutable -> T { } // T is the return type, still lacking throw()

[] là danh sách chụp, () danh sách đối số và {} thân hàm.

Danh sách chụp

Danh sách chụp xác định những gì từ bên ngoài của lambda nên có sẵn bên trong cơ thể chức năng và làm thế nào. Nó có thể là:

  1. một giá trị: [x]
  2. tham chiếu [& x]
  3. bất kỳ biến nào hiện trong phạm vi bằng tham chiếu [&]
  4. giống như 3, nhưng theo giá trị [=]

Bạn có thể kết hợp bất kỳ điều nào ở trên trong danh sách được phân cách bằng dấu phẩy [x, &y].

Danh sách đối số

Danh sách đối số giống như trong bất kỳ hàm C ++ nào khác.

Cơ quan chức năng

Mã sẽ được thực thi khi lambda thực sự được gọi.

Loại khấu trừ

Nếu một lambda chỉ có một câu lệnh return, kiểu trả về có thể được bỏ qua và có kiểu ngầm của decltype(return_statement).

Có thể thay đổi

Nếu một lambda được đánh dấu có thể thay đổi được (ví dụ: []() mutable { }) nó được phép biến đổi các giá trị đã bị bắt bởi giá trị.

Trường hợp sử dụng

Thư viện được định nghĩa bởi tiêu chuẩn ISO được hưởng lợi nhiều từ lambdas và tăng khả năng sử dụng một số thanh như bây giờ người dùng không phải làm lộn xộn mã của họ với các functors nhỏ trong một phạm vi truy cập nào đó.

C ++ 14

Trong C ++ 14 lambdas đã được mở rộng bằng các đề xuất khác nhau.

Khởi tạo Lambda Captures

Một phần tử của danh sách chụp có thể được khởi tạo với =. Điều này cho phép đổi tên các biến và chụp bằng cách di chuyển. Một ví dụ lấy từ tiêu chuẩn:

int x = 4;
auto y = [&r = x, x = x+1]()->int {
            r += 2;
            return x+2;
         }();  // Updates ::x to 6, and initializes y to 7.

và một được lấy từ Wikipedia cho thấy cách chụp std::move:

auto ptr = std::make_unique<int>(10); // See below for std::make_unique
auto lambda = [ptr = std::move(ptr)] {return *ptr;};

Chung Lambdas

Lambdas bây giờ có thể là chung chung (auto sẽ tương đương với T ở đây nếu T là một đối số mẫu kiểu ở đâu đó trong phạm vi xung quanh):

auto lambda = [](auto x, auto y) {return x + y;};

Cải thiện trả lại loại khấu trừ

C ++ 14 cho phép loại bỏ các kiểu trả về cho mọi hàm và không giới hạn nó cho các hàm của biểu mẫu return expression;. Điều này cũng được mở rộng cho lambdas.


719
2017-10-02 15:43



Trong ví dụ của bạn cho các capture lambda khởi tạo ở trên, tại sao bạn kết thúc hàm lamba với () ;? Điều này xuất hiện như [] () {} (); thay vì [](){};. Cũng không nên giá trị của x là 5? - Ramakrishnan Kannan
@RamakrishnanKannan: 1) () là có để gọi lambda ngay sau khi xác định nó và cung cấp cho y giá trị trả lại của nó. Biến y là một số nguyên chứ không phải lambda. 2) Không, x = 5 là cục bộ cho lambda (một giá trị bắt giữ chỉ xảy ra có cùng tên với biến phạm vi bên ngoài x), và sau đó x + 2 = 5 + 2 được trả về. Việc gán lại biến ngoài x xảy ra thông qua tham chiếu r: r = &x; r += 2;, nhưng điều này xảy ra với giá trị ban đầu là 4. - The Vee


Các biểu thức lambda thường được sử dụng để đóng gói các thuật toán để chúng có thể được truyền cho một hàm khác. Tuy nhiên, có thể thực hiện một lambda ngay lập tức theo định nghĩa:

[&](){ ...your code... }(); // immediately executed lambda expression

có chức năng tương đương với

{ ...your code... } // simple code block

Điều này làm cho các biểu thức lambda một công cụ mạnh mẽ để tái cấu trúc các hàm phức tạp. Bạn bắt đầu bằng cách gói một phần mã trong một hàm lambda như được hiển thị ở trên. Quá trình tham số rõ ràng sau đó có thể được thực hiện dần dần với thử nghiệm trung gian sau mỗi bước. Một khi bạn đã mã hóa đầy đủ tham số (như được minh họa bằng cách loại bỏ &), bạn có thể di chuyển mã đến một vị trí bên ngoài và biến nó thành một hàm bình thường.

Tương tự, bạn có thể sử dụng biểu thức lambda để khởi tạo biến dựa trên kết quả của thuật toán...

int a = []( int b ){ int r=1; while (b>0) r*=b--; return r; }(5); // 5!

Như một cách phân vùng logic chương trình của bạn, thậm chí bạn có thể thấy hữu ích khi truyền một biểu thức lambda như một đối số cho một biểu thức lambda khác ...

[&]( std::function<void()> algorithm ) // wrapper section
   {
   ...your wrapper code...
   algorithm();
   ...your wrapper code...
   }
([&]() // algorithm section
   {
   ...your algorithm code...
   });

Biểu thức Lambda cũng cho phép bạn tạo tên hàm lồng nhau, đó có thể là cách thuận tiện để tránh trùng lặp logic. Sử dụng lambdas có tên cũng có xu hướng dễ dàng hơn một chút trên đôi mắt (so với lambdas nội tuyến ẩn danh) khi truyền một hàm không tầm thường như một tham số cho một hàm khác. Lưu ý: đừng quên dấu chấm phẩy sau khi đóng ngoặc nhọn.

auto algorithm = [&]( double x, double m, double b ) -> double
   {
   return m*x+b;
   };

int a=algorithm(1,2,3), b=algorithm(4,5,6);

Nếu hồ sơ tiếp theo cho thấy chi phí khởi tạo đáng kể cho đối tượng hàm, bạn có thể chọn viết lại điều này như một hàm bình thường.


152
2018-03-01 08:08



Bạn đã nhận ra rằng câu hỏi này đã được hỏi 1,5 năm trước và hoạt động cuối cùng đã gần 1 năm trước chưa? Dù sao, bạn đang đóng góp một số ý tưởng thú vị mà tôi chưa từng thấy trước đây! - Piotr99
Cảm ơn lời khuyên xác định và thực hiện đồng thời! Tôi nghĩ điều đáng chú ý là nó hoạt động như một contidion cho if các câu lệnh: if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespace, giả định i là một std::string - Blacklight Shining
Vì vậy, sau đây là một biểu thức pháp lý: [](){}();. - nobar
Ugh! Python (lambda: None)() cú pháp dễ đọc hơn nhiều. - dan04
@ nobar - bạn nói đúng, tôi đã nhầm lẫn. Điều này là hợp pháp (tôi đã thử nghiệm nó lần này) main() {{{{((([](){{}}())));}}}} - Mark Lakata


Các câu trả lời

Q: Biểu thức lambda trong C ++ 11 là gì?

A: Dưới mui xe, nó là đối tượng của một lớp autogenerated với quá tải toán tử () const. Đối tượng đó được gọi là đóng cửa và được tạo bởi trình biên dịch. Khái niệm 'đóng cửa' này gần với khái niệm liên kết từ C ++ 11. Nhưng lambdas thường tạo ra mã tốt hơn. Và các cuộc gọi thông qua đóng cửa cho phép inlining đầy đủ.

Q: Khi nào tôi sẽ sử dụng nó?

A: Để xác định "logic đơn giản và nhỏ" và yêu cầu trình biên dịch thực hiện thế hệ từ câu hỏi trước. Bạn cung cấp cho trình biên dịch một số biểu thức mà bạn muốn ở bên trong toán tử (). Tất cả các trình biên dịch công cụ khác sẽ tạo ra cho bạn.

Q: Họ giải quyết vấn đề gì mà không thể trước khi giới thiệu của họ?

A: Đó là một số loại đường cú pháp như các toán tử quá tải thay vì các hàm cho tùy chỉnh thêm, subrtact hoạt động ... Nhưng nó tiết kiệm nhiều dòng mã không cần thiết để bọc 1-3 dòng logic thực cho một số lớp, v.v. Một số kỹ sư nghĩ rằng nếu số lượng dòng nhỏ hơn thì có ít cơ hội hơn để tạo ra lỗi trong đó (tôi cũng nghĩ thế)

Ví dụ về cách sử dụng

auto x = [=](int arg1){printf("%i", arg1); };
void(*f)(int) = x;
f(1);
x(1);

Thông tin thêm về lambdas, không được đề cập trong câu hỏi. Bỏ qua phần này nếu bạn không quan tâm

1. Giá trị đã chụp. Những gì bạn có thể chụp

1.1. Bạn có thể tham chiếu đến biến có thời lượng lưu trữ tĩnh trong lambdas. Tất cả đều bị bắt.

1.2. Bạn có thể sử dụng lambda cho các giá trị chụp "theo giá trị". Trong trường hợp này, các vars bị bắt sẽ được sao chép vào đối tượng hàm (đóng).

[captureVar1,captureVar2](int arg1){}

1.3. Bạn có thể chụp được tham chiếu. & - trong ngữ cảnh này có nghĩa là tham chiếu, chứ không phải con trỏ.

   [&captureVar1,&captureVar2](int arg1){}

1.4. Nó tồn tại ký hiệu để nắm bắt tất cả các biến không tĩnh theo giá trị hoặc bằng tham chiếu

  [=](int arg1){} // capture all not-static vars by value

  [&](int arg1){} // capture all not-static vars by reference

1.5. Nó tồn tại ký hiệu để nắm bắt tất cả các tĩnh không theo giá trị, hoặc bằng cách tham chiếu và chỉ định smth. hơn. Ví dụ: Nắm bắt tất cả các vars không tĩnh theo giá trị, nhưng bằng cách chụp tham chiếu Param2

[=,&Param2](int arg1){} 

Nắm bắt tất cả các vars không tĩnh bằng cách tham chiếu, nhưng bằng cách nắm bắt giá trị Param2

[&,Param2](int arg1){} 

2. Loại khấu trừ

2.1. Kiểu trả về lambda có thể được suy ra nếu lambda là một biểu thức. Hoặc bạn có thể chỉ định rõ ràng nó.

[=](int arg1)->trailing_return_type{return trailing_return_type();}

Nếu lambda có nhiều hơn thì một biểu thức, thì kiểu trả về phải được xác định thông qua kiểu trả về đuôi.   Ngoài ra, cú pháp tương tự có thể được áp dụng cho các chức năng tự động và các hàm thành viên

3. Giá trị đã chụp. Những gì bạn không thể chụp

3.1. Bạn chỉ có thể chụp các vars cục bộ chứ không phải biến thành viên của đối tượng.

4. Thay đổi

4.1. lambda không phải là một con trỏ hàm và nó không phải là một hàm ẩn danh, nhưng có thể được chuyển đổi hoàn toàn thành con trỏ hàm.

p.s. 

  1. Thông tin thêm về thông tin ngữ pháp lambda có thể tìm thấy trong Bản thảo làm việc cho Ngôn ngữ lập trình C ++ # 337, 2012-01-16, 5.1.2. Biểu thức Lambda, p.88

  2. Trong C ++ 14, tính năng bổ sung được đặt tên là "init capture" đã được thêm vào. Nó cho phép thực hiện khai báo quân sự của các thành viên dữ liệu đóng cửa:

    auto toFloat = [](int value) { return float(value);};
    auto interpolate = [min = toFloat(0), max = toFloat(255)](int value)->float { return (value - min) / (max - min);};
    

30
2018-06-03 16:40



Tôi đã không bao gồm smth.? Tại sao tôi lại cố gắng trừ đi? - bruziuz
Tôi nối thêm thông tin về chụp, trả lại loại khấu trừ trong C ++ 11 sau khi ai đó cho tôi một dấu trừ. Nó không được đề cập trong câu hỏi, nhưng tôi thêm nó để trả lời! Có lẽ đó là lý do trừ cho bài viết của tôi mà không có "phần bổ sung này ...." - bruziuz
Điều này [&,=Param2](int arg1){} dường như không phải là cú pháp hợp lệ. Biểu mẫu chính xác sẽ là [&,Param2](int arg1){} - GetFree
Cảm ơn. Đầu tiên tôi đã cố gắng biên dịch đoạn mã này. Và có vẻ như sự ám sát lạ trong các biến thể cho phép trong danh sách chụp // g ++ -std = c ++ 11 main.cpp -o test_bin; ./test_bin #include <stdio.h> int main () {#if 1 {int param = 0; auto f = [=, & param] (int arg1) có thể thay đổi {param = arg1;}; f (111); printf ("% i \ n", param); } #endif #if 0 {int param = 0; auto f = [&, = param] (int arg1) có thể thay đổi {param = arg1;}; f (111); printf ("% i \ n", param); } #endif return 0; } - bruziuz
Có vẻ như dòng mới không được hỗ trợ trong nhận xét. Sau đó, tôi đã mở 5.1.2 biểu thức Lambda, p.88, "Bản thảo làm việc, Tiêu chuẩn cho Ngôn ngữ lập trình C ++", Số Dcoument: # 337, 2012-01-16. Và nhìn vào cú pháp ngữ pháp. Và bạn nói đúng. Không tồn tại những thứ như chụp qua "= arg" - bruziuz


Hàm lambda là một hàm ẩn danh mà bạn tạo ra trong dòng. Nó có thể nắm bắt các biến như một số đã giải thích, (ví dụ: http://www.stroustrup.com/C++11FAQ.html#lambda) nhưng có một số hạn chế. Ví dụ, nếu có một giao diện gọi lại như thế này,

void apply(void (*f)(int)) {
    f(10);
    f(20);
    f(30);
}

bạn có thể viết một chức năng ngay tại chỗ để sử dụng nó giống như một hàm được truyền để áp dụng dưới đây:

int col=0;
void output() {
    apply([](int data) {
        cout << data << ((++col % 10) ? ' ' : '\n');
    });
}

Nhưng bạn không thể làm điều này:

void output(int n) {
    int col=0;
    apply([&col,n](int data) {
        cout << data << ((++col % 10) ? ' ' : '\n');
    });
}

vì những hạn chế trong tiêu chuẩn C ++ 11. Nếu bạn muốn sử dụng ảnh chụp, bạn phải dựa vào thư viện và

#include <functional> 

(hoặc một số thư viện STL khác như thuật toán để lấy nó gián tiếp) và sau đó làm việc với std :: function thay vì truyền các hàm bình thường như các tham số như sau:

#include <functional>
void apply(std::function<void(int)> f) {
    f(10);
    f(20);
    f(30);
}
void output(int width) {
    int col;
    apply([width,&col](int data) {
        cout << data << ((++col % width) ? ' ' : '\n');
    });
}

12
2018-03-10 22:36



lý do là, một lambda chỉ có thể chuyển đổi thành một con trỏ hàm, nếu nó không có capture. nếu apply là một khuôn mẫu chấp nhận một functor, nó sẽ hoạt động - sp2danny
Nhưng vấn đề là nếu áp dụng là một giao diện hiện có, bạn có thể không có sự sang trọng của việc có thể tuyên bố nó khác với một hàm cũ đơn giản. Tiêu chuẩn này có thể được thiết kế để cho phép một thể hiện mới của một hàm cũ đơn giản được tạo ra mỗi lần biểu thức lambda như vậy được thực thi, với các tham chiếu được mã hóa cứng đến các biến bị bắt. Có vẻ như một hàm lambda được tạo ra tại thời gian biên dịch. Cũng có những hậu quả khác. ví dụ: Nếu bạn khai báo biến tĩnh, ngay cả khi bạn đánh giá lại biểu thức lambda, bạn không nhận được biến tĩnh mới. - Ted
con trỏ hàm thường có nghĩa là được lưu, và một capture lambdas có thể đi ra ngoài phạm vi. chỉ có lambdas chụp ít chuyển đổi thành con trỏ hàm bằng thiết kế - sp2danny
Bạn vẫn phải chú ý đến các biến ngăn xếp đang được deallocated cho cùng một lý do một trong hai cách. Xem blogs.msdn.com/b/nativeconcurrency/archive/2012/01/29/…  Ví dụ tôi đã viết với đầu ra và áp dụng được viết để nếu thay vào đó con trỏ hàm được cho phép và sử dụng, chúng cũng sẽ hoạt động. Col vẫn được cấp phát cho đến sau khi tất cả các cuộc gọi hàm từ áp dụng đã kết thúc. Làm thế nào bạn sẽ viết lại mã này để làm việc bằng cách sử dụng giao diện hiện có áp dụng? Bạn sẽ kết thúc bằng cách sử dụng biến toàn cầu hoặc tĩnh, hoặc một số chuyển đổi tối nghĩa của mã? - Ted
hoặc có lẽ bạn chỉ đơn giản có nghĩa là các biểu thức lambda là các giá trị và do đó tạm thời, nhưng mã vẫn không đổi (singleton / static) để nó có thể được gọi trong tương lai. Trong trường hợp đó, có lẽ chức năng sẽ vẫn được cấp phát miễn là các ảnh chụp được cấp phát xếp chồng của nó vẫn được cấp phát. Tất nhiên nó có thể nhận được lộn xộn unwinding nó nếu ví dụ nhiều biến thể của chức năng được phân bổ trong một vòng lặp. - Ted


Một trong những giải thích tốt nhất của lambda expression được đưa ra từ tác giả của C ++ Bjarne Stroustrup trong cuốn sách của anh ấy ***The C++ Programming Language*** chương 11 (ISBN-13: 978-0321563842):

What is a lambda expression? 

A biểu thức lambda, đôi khi còn được gọi là lambda   chức năng hoặc (nói đúng không chính xác, nhưng thông tục) như một    lambda, là một ký hiệu đơn giản để xác định và sử dụng đối tượng hàm ẩn danh. Thay vì định nghĩa một lớp có tên với toán tử (), sau đó tạo một đối tượng của lớp đó, và cuối cùng   gọi nó, chúng ta có thể sử dụng một cách viết tắt.

When would I use one?

Điều này đặc biệt hữu ích khi chúng ta muốn chuyển một thao tác như một   đối số cho một thuật toán. Trong bối cảnh giao diện người dùng đồ họa   (và ở nơi khác), các hoạt động như vậy thường được gọi là callbacks.

What class of problem do they solve that wasn't possible prior to their introduction?

Ở đây tôi đoán mọi hành động được thực hiện với biểu thức lambda có thể được giải quyết mà không có chúng, nhưng với nhiều mã hơn và phức tạp hơn nhiều. Biểu thức Lambda đây là cách tối ưu hóa cho mã của bạn và cách làm cho nó trở nên hấp dẫn hơn. Như buồn bởi Stroustup:

cách tối ưu hóa hiệu quả

Some examples

thông qua biểu thức lambda

void print_modulo(const vector<int>& v, ostream& os, int m) // output v[i] to os if v[i]%m==0
{
    for_each(begin(v),end(v),
        [&os,m](int x) { 
           if (x%m==0) os << x << '\n';
         });
}

hoặc thông qua chức năng

class Modulo_print {
         ostream& os; // members to hold the capture list int m;
     public:
         Modulo_print(ostream& s, int mm) :os(s), m(mm) {} 
         void operator()(int x) const
           { 
             if (x%m==0) os << x << '\n'; 
           }
};

hoặc thậm chí

void print_modulo(const vector<int>& v, ostream& os, int m) 
     // output v[i] to os if v[i]%m==0
{
    class Modulo_print {
        ostream& os; // members to hold the capture list
        int m; 
        public:
           Modulo_print (ostream& s, int mm) :os(s), m(mm) {}
           void operator()(int x) const
           { 
               if (x%m==0) os << x << '\n';
           }
     };
     for_each(begin(v),end(v),Modulo_print{os,m}); 
}

nếu bạn cần u có thể đặt tên lambda expression như dưới đây:

void print_modulo(const vector<int>& v, ostream& os, int m)
    // output v[i] to os if v[i]%m==0
{
      auto Modulo_print = [&os,m] (int x) { if (x%m==0) os << x << '\n'; };
      for_each(begin(v),end(v),Modulo_print);
 }

Hoặc giả sử một mẫu đơn giản khác

void TestFunctions::simpleLambda() {
    bool sensitive = true;
    std::vector<int> v = std::vector<int>({1,33,3,4,5,6,7});

    sort(v.begin(),v.end(),
         [sensitive](int x, int y) {
             printf("\n%i\n",  x < y);
             return sensitive ? x < y : abs(x) < abs(y);
         });


    printf("sorted");
    for_each(v.begin(), v.end(),
             [](int x) {
                 printf("x - %i;", x);
             }
             );
}

sẽ tạo ra tiếp theo

0

1

0

1

0

1

0

1

0

1

0 được sắp xếp x - 1, x - 3, x - 4, x - 5, x - 6, x - 7, x - 33;

[] - đây là danh sách chụp hoặc lambda introducer: nếu lambdas yêu cầu không có quyền truy cập vào môi trường địa phương của họ, chúng tôi có thể sử dụng nó.

Trích dẫn từ sách:

Ký tự đầu tiên của biểu thức lambda luôn là [. Một lambda   người giới thiệu có thể có nhiều dạng khác nhau:

[]: một danh sách chụp trống. Điều này   ngụ ý rằng không có tên địa phương nào từ ngữ cảnh xung quanh có thể được sử dụng   trong cơ thể lambda. Đối với các biểu thức lambda như vậy, dữ liệu thu được từ   đối số hoặc từ các biến phi địa phương.

[&]: nắm bắt hoàn toàn bởi   tài liệu tham khảo. Tất cả các tên địa phương có thể được sử dụng. Tất cả các biến cục bộ là   truy cập bằng tham chiếu.

[=]: ẩn theo giá trị. Tất cả địa phương   tên có thể được sử dụng. Tất cả các tên đều đề cập đến các bản sao của các biến cục bộ   được thực hiện tại điểm gọi của biểu thức lambda.

[danh sách chụp]:  chụp rõ ràng; danh sách chụp là danh sách các tên của các biến cục bộ cần được ghi lại (tức là, được lưu trữ trong đối tượng) theo tham chiếu hoặc theo giá trị. Các biến có tên đứng trước và được ghi lại bởi   tài liệu tham khảo. Các biến khác được ghi lại theo giá trị. Danh sách chụp có thể   cũng chứa tên này và các tên được theo sau bởi ... thành phần tử.

[&, danh sách chụp]: ẩn hoàn toàn bằng cách tham chiếu tất cả các biến cục bộ có tên không được đặt trong danh sách. Danh sách chụp có thể chứa điều này. Các tên được liệt kê không thể đứng trước bởi &. Các biến được đặt tên trong   danh sách chụp được ghi lại theo giá trị.

[=, danh sách chụp]: nắm bắt hoàn toàn theo giá trị tất cả các biến cục bộ có tên không được đề cập trong danh sách. Danh sách chụp không thể chứa nội dung này. Các tên được liệt kê phải đứng trước &. Các biến thể được đặt tên trong danh sách chụp được ghi lại bằng tham chiếu.

Lưu ý rằng tên cục bộ đứng trước & luôn bị bắt bởi   tham chiếu và tên địa phương không được đặt trước bởi & luôn bị bắt bởi   giá trị. Chỉ chụp bằng tham chiếu cho phép sửa đổi các biến trong   môi trường gọi điện.

Additional

Lambda expression định dạng

enter image description here

Tài liệu tham khảo bổ sung:


6
2017-11-09 11:02





Một vấn đề nó giải quyết: Mã đơn giản hơn lambda cho một cuộc gọi trong hàm tạo có sử dụng hàm tham số đầu ra để khởi tạo một thành viên const

Bạn có thể khởi tạo một thành viên const của lớp của bạn, với một cuộc gọi đến một hàm đặt giá trị của nó bằng cách trả lại đầu ra của nó như là một tham số đầu ra.


1
2018-06-27 00:38



Điều này cũng có thể được thực hiện với một hàm đơn giản, đó thậm chí còn là câu trả lời được chấp nhận cho câu hỏi mà bạn liên kết với nói để làm. - SirGuy


Vâng, một trong những thực tế sử dụng tôi đã phát hiện ra là giảm mã tấm nồi hơi. Ví dụ:

void process_z_vec(vector<int>& vec)
{
  auto print_2d = [](const vector<int>& board, int bsize)
  {
    for(int i = 0; i<bsize; i++)
    {
      for(int j=0; j<bsize; j++)
      {
        cout << board[bsize*i+j] << " ";
      }
      cout << "\n";
    }
  };
  // Do sth with the vec.
  print_2d(vec,x_size);
  // Do sth else with the vec.
  print_2d(vec,y_size);
  //... 
}

Nếu không có lambda, bạn có thể cần phải làm một cái gì đó cho khác nhau bsize các trường hợp. Tất nhiên bạn có thể tạo ra một hàm nhưng nếu bạn muốn giới hạn việc sử dụng trong phạm vi chức năng người dùng linh hồn thì sao? bản chất của lambda đáp ứng yêu cầu này và tôi sử dụng nó cho trường hợp đó.


1
2017-11-23 09:16