C++ lambda
C++
30
White

cpplover viết ngày 08/09/2015

C++ 11 có chức năng lambda. Vì là ngôn ngữ "static type" nên C++ lambda function nhìn khó hơn lambda trong các ngôn ngữ dynamic type khác nhưng python hay javascripts. Nhiều cậu ngày trước ác cảm với C++, nhìn thấy lamda không hiểu, thấy rối rối lại càng ghét C++ hơn :))) Tuy vậy, nếu biết cách tiếp cận step-by-step một chút, thì C++ lambda cũng chẳng khó hiểu cũng nhưng chẳng rối đến thế. Tớ sẽ giải thích lambda dễ hiểu để cho mấy cậu suốt ngày mở mồm chê C++ phải ngậm miệng :))

Bắt chước style bài Javascript của cậu @studybot

Định nghĩa

Lambda expression (có tên khác như: hàm không tên :)) -- anonymous function, : là cách viết định nghĩa một hàm trong một ngôn ngữ.

Đọc là: lam đa ếch xừ pừ rét s sừn.

Cách gọi khác: hàm không tên -- anonymous function.

Khái quát

Lambda là đối tượng hàm (Function Object) , là operator() của một object. Lambda có thể được dùng thay thế cho std::function và có thể dùng với cả bộ thư viện STL (Các cậu sẽ thấy điều này tiện lợi thế nào). Lamda trong C++ có thể capture được các biến local (sẽ trình bày sau). Giá trị trả về của lambda có thể được được suy luận kiểu (có nghĩa là dùng được từ khóa auto).

Do lambda là tính năng mới của C++ (C++ 11) nên để dùng tính năng của ngôn ngữ này, ta phải kiếm 1 compiler mơi mới, xịn xịn. Tớ dùng VisualC++ 2015 bản community thấy chạy được. Bạn nào dùng Linux thì dùng GCC 4.5 trở lên. LLVM Clang (bản mới nhất) chắc chắn có hỗ trợ lambda.

Cú pháp

C++ lambda có 4 cú pháp khai báo như sau (nhìn khá phức tạp):

Tham khảo tại C++ reference

[ capture-list ] ( params ) mutable(optional) exception attribute -> ret { body }   (1)     
[ capture-list ] ( params ) -> ret { body }     (2)     
[ capture-list ] ( params ) { body }    (3)     
[ capture-list ] { body }

capture-list là gì? @.@, mutable là cái gì, ret là cái gì ?

Từ từ cho khoai nó nhừ hehe. Bắt đầu triệt hạ từng thằng bằng các ví dụ nhỏ sau đây.

Ví dụ

1. Lambda đơn giản

Hàm này chả đem lại lợi ích gì cho xã hội, chỉ là 3 cái ngoặc đứng chiếm diện tích. Ấy vậy mà chương trình compilechạy như thường.

int main() {
    [](){};
    return 0;
}

2. Lamda đơn giản hơn nữa! (đơn giản nhất)

int main() {
    []{};
    return 0;
}

Trong dấu () là đối số của lambda. Ở đây bản thân lambda này không nhận đối số nên có thể bỏ luôn được cả ngoặc.

3. Ý nghĩa của từng cái ngoặc

int main() {
    []      // Khai báo lambda
    ()      // Đối số
    {}     // Thân hàm
    ();     // Chạy!
    return 0;
}

4. Hello lambda

#include<iostream>

int main() {
    []{ std::cout << "Hello Lambda\n"; }();
    return 0;
}

Biên dịch và chạy sẽ thấy dòng "Hello Lambda" được xuất ra màn hình.

5. Truyền đối số

#include<iostream>

int main() {
    [](const std::string& x) { std::cout << x << std::endl; }("Hello lambda\n");
    return 0;
}

6. Trả về giá trị

#include<iostream>

int main() {
    auto x = [](int a) -> int { return a + 5; } (10);
    std::cout << x;
    return 0;
}

Kết quả là 15. Ở đây -> int là cách viết diễn đạt là hàm này sẽ trả về kiểu int. auto sẽ tự suy luận kiểu của x.

7. Capture / Capture

Trước khi ví dụ chắc phải giải thích trước thì ví dụ sẽ dễ hiểu hơn. Lambda có đặc điểm là nó là Function Object và truy cập được biến số ngoài scope (Scope tạo bởi dấu {}) của nó. Truy cập một biến được capture có 2 cách:

  • Truy cập theo reference.
  • Truy cập bằng cách copy (by value).

Trong Lambda, để capture by reference thì viết [&]. Để capture bằng value thì viết [=]. Để không capture gì cả thì để trống [].

#include<iostream>

int main() {
    int a = 5;
    [&] { std::cout << a << std::endl; }();   // a sẽ được truyền bằng reference.
    [=] { std::cout << a << std::endl; }();    // a sẽ được truyền bằng value.
    return 0;
}

8. Ghi đè

Biến số được capture bằng tham chiếu có thể bị ghi đè.

#include<iostream>

int main() {
    int a = 5;
    [&] { a = a + 1; std::cout << a << std::endl; }();   // --> In 6    
    return 0;
}

9. Capture bằng value sẽ báo lỗi khi compile.

Lỗi compile

#include<iostream>

int main() {
    int a = 5;
    [=] { a = a + 1; std::cout << a << std::endl; }();   // --> In 6    
    return 0;
}

10. Nếu vẫn muốn sửa giá trị capture bằng value? --> Dùng mutable

Biến a được truyền bằng giá trị và mutable nên trong lambda giá trị bị thay đổi nhưng khi ra khỏi scope của lambda, mọi việc lại trở lại bình thường.

#include<iostream>

int main() {
    int a = 5;
    [=]() mutable { a = a + 1; std::cout << a << std::endl; }();   // In 6
    std::cout << a << std::endl;   // In 5
    return 0;
}

#### 11. Sự khác nhau giữa reference và mutable value

 #include<iostream>

int main() {
     int a = 0, b = 0;
    [a, &b]() mutable { a = 1; b = 1; } ();
    std::cout << a << std::endl; // in 0
    std::cout << b << std::endl; // in 1
}

12. Capture capture những gì?

Capture nếu không chỉ rõ tên biến capture tất cả những gì có thể capture được.

  1. Capture a bằng giá trị và b bằng reference. Không capture c, d
 #include<iostream>

int main() {
     int a = 0, b = 0, c = 0, d = 0;
    [a, &b]() mutable {}
}
  1. Capture c, d bằng giá trị, a, b bằng reference.
 #include<iostream>

int main() {
     int a = 0, b = 0, c = 0, d = 0;
    [=, &a, &b]() mutable  {}
}
  1. Capture tất cả
 #include<iostream>

int main() {
     int a = 0, b = 0, c = 0, d = 0;
    [&]() mutable {}   // Capture a, b, c, d bằng reference.
    [=]() mutable {}   // Capture a, b, c, d bằng value.
}

13. Dùng Lambda và std::function

#include <iostream>
#include <string>
#include <functional>

std::function <void()> f() {
    std::string h("Hello WOrld");
    return [=] { std::cout << h << std::endl; };
}

int main()
{
    auto func = f();
    func();   // in Hello WOrld
    f()();       // in Hello WOrld
    getchar();
    return 0;
}

Hàm f khi được chạy f() sẽ trả về 1 std::function mà có kiểu trả về là void. f()() sẽ chạy hàm f, nhận về 1 hàm anonymous và chạy hàm này.

14. Dùng lambda với stl

#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>

struct Sum {
    Sum() { sum = 0; }
    void operator()(int n) { sum += n; }
    int sum;
};

int main()
{
    std::vector<int> nums{ 3, 4, 5, 6, 7, 8 };
    for (auto n : nums)
        std::cout << " " << n;
    std::cout << std::endl;

    Sum s = std::for_each(nums.begin(), nums.end(), Sum());
    std::cout << s.sum << std::endl;

    int sum = 0;
    std::for_each(nums.begin(), nums.end(), [&sum](int i) { sum += i; });
    std::cout << sum << std::endl;

    getchar();
    return 0;
}

Để gọi for_each theo cách truyền thống ta phải định nghĩa Function Object và hoặc truyển 1 function đã được định nghĩa sẵn vào hàm đấy. Dùng lambda capture 1 biến khai báo ở scope cao hơn (biến sum), ta giải quyết được bài toán chỉ trong 1 dòng. Kết quả không khác gì cách viết truyển thống.

Tóm gọn

Dùng lambda, dùng Lambda, dùng LAMBDA!

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

cpplover

12 bài viết.
47 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
16 2
Giới thiệu Cách duy nhất để học C++ trong 21 ngày là dành hơn 14611 ngày để nghiên cứu và thay thế chính bạn ở ngày số 21 (Ảnh) C++ là một ngôn ...
cpplover viết hơn 3 năm trước
16 2
White
16 0
Lang thang hacker news tìm được một phần nhỏ (Link) của một bác ở Anh tổng hợp về stack mà google đã tạo và sử dụng. Chi tiết về các stack được tổ...
cpplover viết hơn 3 năm trước
16 0
White
11 1
Chức năng pattern matching trong C++ thấy nói sẽ kill (Link). Cách viết C++ do vậy sẽ thay đổi tương đối. Tự nhiên tôi thấy phải nhìn lại visitor ...
cpplover viết hơn 3 năm trước
11 1
Bài viết liên quan
Male avatar
0 0
Constructor của lớp cha luôn được gọi trước constructor của lớp con. class Foo { public: Foo() { cout << "Base class initializing" << endl; ...
baoquocphan viết hơn 1 năm trước
0 0
White
11 1
Chức năng pattern matching trong C++ thấy nói sẽ kill (Link). Cách viết C++ do vậy sẽ thay đổi tương đối. Tự nhiên tôi thấy phải nhìn lại visitor ...
cpplover viết hơn 3 năm trước
11 1
White
18 0
Nhân tiện vừa đọc bài viết liên quan tới OpenCV trên Kipalog, nên em xin giới thiệu về giải thuật sinh ảnh mosaic từ một ảnh gốc. Không hiểu sinh ả...
Cùi Bắp viết hơn 2 năm trước
18 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
12 bài viết.
47 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!