Làm thế nào để khai báo một hàm chấp nhận lambda?


82

Tôi đã đọc trên internet nhiều hướng dẫn giải thích cách sử dụng lambda với thư viện chuẩn (chẳng hạn như std::find), và tất cả chúng đều rất thú vị, nhưng tôi không thể tìm thấy bất kỳ hướng dẫn nào giải thích cách tôi có thể sử dụng lambda cho các chức năng của riêng mình.

Ví dụ:

int main()
{
    int test = 5;
    LambdaTest([&](int a) { test += a; });

    return EXIT_SUCCESS;
}

Tôi nên khai báo LambdaTestnhư thế nào? Loại đối số đầu tiên của nó là gì? Và sau đó, làm thế nào tôi có thể gọi hàm ẩn danh truyền cho nó - ví dụ - "10" làm đối số của nó?

Câu trả lời:


78

Cho rằng bạn có thể cũng muốn chấp nhận các con trỏ hàm và các đối tượng hàm ngoài lambdas, có thể bạn sẽ muốn sử dụng các mẫu để chấp nhận bất kỳ đối số nào với an operator(). Đây là những gì các hàm std như find làm. Nó sẽ trông như thế này:

template<typename Func>
void LambdaTest(Func f) {
    f(10);
}

Lưu ý rằng định nghĩa này không sử dụng bất kỳ tính năng nào của c ++ 0x, vì vậy nó hoàn toàn tương thích ngược. Đó chỉ là lệnh gọi hàm sử dụng các biểu thức lambda dành riêng cho c ++ 0x.


2
Trong trường hợp có lỗi, các thông báo lỗi sẽ rất khó hiểu.
liori

13
Nó phụ thuộc vào việc nó là tốt nhất. Cái này sử dụng một mẫu, còn cái kia thì không. Điều này có nghĩa là hàm không thể ảo nữa và không thể được định nghĩa riêng trong tệp cpp. std::functionhoàn toàn có thể nhận các kiểu lớp đối tượng hàm, mặc dù hơi chậm hơn khi gọi. Nhưng sự khác biệt đó là không đáng kể đối với hầu hết các ứng dụng :)
Johannes Schaub - litb

2
"tốt nhất" là trong mắt người xem :-) câu trả lời này sử dụng một câu trả lời functorrất hay, nhưng không thực sự trả lời câu hỏi ban đầu (và điều gì đã dẫn tôi đến đây) là "làm cách nào để sử dụng lambda cho các chức năng của riêng tôi" . Ngoài ra, các mẫu có tập hợp các vấn đề riêng, mà câu trả lời std::functionlà không.
Marco Massenzio

1
@Marco Câu trả lời này không yêu cầu bạn sử dụng chức năng, nó cho phép bạn sử dụng bất kỳ thứ gì bạn muốn - bao gồm lambdas.
sepp2k

68

Nếu bạn không muốn tạo mẫu mọi thứ, bạn có thể làm như sau:

void LambdaTest (const std::function <void (int)>& f)
{
    ...
}

1
Cú pháp này thực sự cho phép tôi lưu biến hàm để gọi nó sau này, phải không? Ví dụ: tôi muốn triển khai một hàm cho phép thực thi các truy vấn cơ sở dữ liệu không đồng bộ, trong đó lambda hoạt động như một lệnh gọi lại. (Tất nhiên là tôi sẽ không thể truy cập các điểm đóng cửa bằng cách tham khảo)
Thomas Bonini

1
Truyền các hàm theo giá trị không phải là dễ hiểu hơn sao?
fredoverflow

1
@Andreas Bonini: Có, nếu bạn lưu vào std::function(không phải tham chiếu), bạn tạo một bản sao của f. Tuy nhiên, tôi không chắc lambda / bao đóng xử lý tham chiếu như thế nào khi đối tượng được tham chiếu vượt ra ngoài phạm vi, có thể là UB. @FredOverflow: Sự hiểu biết của tôi là đó std::functionkhông phải là một đối tượng tầm thường, đặc biệt là khi gói lambdas. Có lẽ tốt hơn là nên tham khảo để tránh sao chép không cần thiết.
doublep

Nếu lambda của bạn nắm bắt ngăn xếp theo giá trị thì có, lambda có thể tồn tại lâu hơn các biến đó và sẽ tiếp tục giữ các bản sao của chúng. Nếu nó nắm bắt bằng cách tham khảo, bạn sẽ có một vấn đề.
Kate Gregory

1
Chúng tôi sẽ phải sao chép nó bên trong hàm, vì vậy không có ích gì khi sao chép nó khi nó được chuyển vào
Casebash

9

Tôi muốn đóng góp ví dụ đơn giản nhưng tự giải thích này. Nó chỉ ra cách truyền "những thứ có thể gọi" (hàm, đối tượng hàm và lambdas) cho một hàm hoặc một đối tượng.

// g++ -std=c++11 thisFile.cpp

#include <iostream>
#include <thread>

using namespace std;

// -----------------------------------------------------------------
class Box {
public:
  function<void(string)> theFunction; 
  bool funValid;

  Box () : funValid (false) { }

  void setFun (function<void(string)> f) {
    theFunction = f;
    funValid = true;
  }

  void callIt () {
    if ( ! funValid ) return;
    theFunction (" hello from Box ");
  }
}; // class

// -----------------------------------------------------------------
class FunClass {
public:
  string msg;
  FunClass (string m) :  msg (m) { }
  void operator() (string s) {
    cout << msg <<  s << endl; 
  }
};

// -----------------------------------------------------------------
void f (string s) {
  cout << s << endl;
} // ()

// -----------------------------------------------------------------
void call_it ( void (*pf) (string) ) {
  pf( "call_it: hello");
} // ()

// -----------------------------------------------------------------
void call_it1 ( function<void(string)> pf ) {
  pf( "call_it1: hello");
} // ()

// -----------------------------------------------------------------
int main() {

  int a = 1234;

  FunClass fc ( " christmas ");

  f("hello");

  call_it ( f );

  call_it1 ( f );

  // conversion ERROR: call_it ( [&] (string s) -> void { cout << s << a << endl; } );

  call_it1 ( [&] (string s) -> void { cout << s << a << endl; } );

  Box ca;

  ca.callIt ();

  ca.setFun (f);

  ca.callIt ();

  ca.setFun ( [&] (string s) -> void { cout << s << a << endl; } );

  ca.callIt ();

  ca.setFun (fc);

  ca.callIt ();

} // ()

2
Bạn không cần phải funValid: en.cppreference.com/w/cpp/utility/functional/function/... , chỉ cần nóiif ( ! theFunction )
Erik Aronesty
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.