第 18.11 節

Lambda expression

0瀏覽次數0訪問次數--跳出率--平均停留

What problem does this section solve?

Many places require passing in a small piece of logic: sorting rules, filtering conditions, button callbacks, timer callbacks. The old approach typically has three methods:

  1. Write an ordinary function.
  2. Write a function pointer.
  3. Write a function object, which is a class or struct with operator().

All these approaches work, but small logic gets forced to distant locations, or an extra type has to be written. Lambda expressions allow you to write a small function on the spot where it's used and can capture external variables.

What is this feature?

Lambda is an anonymous callable object. It resembles a function but is essentially a class object generated by the compiler.

The basic structure is:

PartexampleMeaning
capture list[x, &y]Which variables to take from the external scope?
parameter list(int a, int b)When invoking a lambda function, you typically pass parameters that define the input data for the function to process. The exact structure depends on the platform or runtime environment you're using. Here are the most common scenarios:

1. AWS Lambda (or similar serverless platforms)

  • event: A JSON-serializable object that contains the input data for the function. This could be an API Gateway request, S3 event, DynamoDB stream, or custom event.
  • context: An object provided by the runtime containing runtime information (e.g., function name, request ID, time remaining).
  • Example invocation:
    {
      "key1": "value1",
      "key2": "value2",
      "key3": "value3"
    }
    

2. General Programming (e.g., Python, JavaScript)

  • In languages like Python, a lambda is an anonymous function that can take arguments.
  • Example:
    square = lambda x: x ** 2
    result = square(5)  # Pass '5' as the argument
    

3. Cloud Functions (e.g., Google Cloud Functions, Azure Functions)

  • Similar to AWS Lambda, they typically expect an event object and sometimes a context or callback.
  • The event structure is often predefined by the trigger (e.g., HTTP request, Pub/Sub message).

4. Custom/In-house Systems

  • You may define your own invocation protocol. Common patterns include:
    • JSON payload: { "action": "process", "data": {...} }
    • Simple arguments: my_lambda(arg1, arg2)
    • Message queue: Sending a serialized message that the lambda consumes.

Key Tips:

  • Check documentation: The exact parameters depend on how the lambda was deployed and its trigger.
  • Start simple: Often, the event object contains the essential data needed for processing.
  • Use context wisely: The context object (if available) helps with logging, timeouts, and other runtime details.

If you have a specific platform or use case in mind, I can provide more tailored details!| |return value|-> int|Return type, often can be omitted| |Function body|{ return a + b; }|The code that is actually executed|

The complete form can be written as [x](int n) -> int { return x + n; }, and in common cases, the return type can be omitted.

C++ standard version

  • C++11: Basic Lambda.
  • C++14: Generic Lambdas, where parameters can be specified as auto.
  • C++17: constexpr Lambda, [*this] capture.

Lambda is a language feature and does not require extra header files. Only when used in conjunction with tools from libraries such as STL algorithms and std::function do you need to include the corresponding header files.

Capture List Quick Reference

writing methodMeaningApplicable Scenarios
[]Do not capture external variables.Only use parameters or local temporary variables.
[x]Capture by value xSave a copy, suitable for saving callbacks.
[&x]Capture by reference xNeed to modify external variables while ensuring their lifecycle.
[x, &y]Hybrid CaptureClarify which are copies and which are references
[=]captures used variables by value by defaultSmall examples are convenient for learning, but avoid overusing them in actual projects.
[&]Capture used variables by defaultEspecially dangerous in asynchronous or saved callbacks.
[this]Capture the current object pointerObjects must outlive lambdas.
[*this]Capture a copy of the current objectAvailable since C++17, useful for avoiding dangling references

Example code

Example 1: Differences Between the Old Callback Style and Lambda Expressions

Both regular functions and function objects can serve as algorithm conditions, but Lambda is more suitable for writing short local logic.

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

bool is_even(int n)
{
    return n % 2 == 0;
}

struct GreaterThan
{
    int limit;

    bool operator()(int n) const
    {
        return n > limit;
    }
};

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // vector 是动态数组,元素数量可以在运行时变化。
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6};

    int even_count = std::count_if(numbers.begin(), numbers.end(), is_even);
    std::cout << "even count = " << even_count << "\n";

    int greater_count1 = std::count_if(numbers.begin(), numbers.end(), GreaterThan{3});
    std::cout << "> 3 count (functor) = " << greater_count1 << "\n";

    int limit = 3;
    int greater_count2 = std::count_if(numbers.begin(), numbers.end(),
                                       [limit](int n) {
                                           return n > limit;
                                       });
    std::cout << "> 3 count (lambda) = " << greater_count2 << "\n";

    return 0;
}

Results

even count = 3
> 3 count (functor) = 3
> 3 count (lambda) = 3

Example 2: Basic Lambda Syntax, Parameters, and Return Values

#include <iostream>
#include <string>

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    auto add = [](int a, int b) {
        return a + b;
    };

    auto describe_score = [](int score) -> std::string {
        if (score >= 60)
        {
            return "pass";
        }
        return "fail";
    };

    std::cout << "add(3, 5) = " << add(3, 5) << "\n";
    std::cout << "score 80 is " << describe_score(80) << "\n";
    std::cout << "score 40 is " << describe_score(40) << "\n";

    // 返回 0 表示程序正常结束。
    return 0;
}

Results

add(3, 5) = 8
score 80 is pass
score 40 is fail

Example 3: capture by value and capture by reference

Capturing by value saves a copy at the time the lambda is defined; capturing by reference accesses the external variable itself.

#include <iostream>

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    int score = 10;

    auto add_by_value = [score](int bonus) {
        return score + bonus;
    };

    auto add_by_ref = [&score](int bonus) {
        score += bonus;
        return score;
    };

    score = 20;

    std::cout << "value capture result = " << add_by_value(5) << "\n";
    std::cout << "ref capture result = " << add_by_ref(5) << "\n";
    std::cout << "score after ref capture = " << score << "\n";

    // 返回 0 表示程序正常结束。
    return 0;
}

Results

value capture result = 15
ref capture result = 25
score after ref capture = 25

Example 4: mutable allows modification of copies of variables captured by value

Variables captured by value are read-only by default inside a lambda. With mutable, you can modify the copy stored by the lambda itself, but it won't affect the outer variable.

#include <iostream>

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    int start = 0;

    auto counter = [start]() mutable {
        ++start;
        return start;
    };

    std::cout << "counter() = " << counter() << "\n";
    std::cout << "counter() = " << counter() << "\n";
    std::cout << "outside start = " << start << "\n";

    // 返回 0 表示程序正常结束。
    return 0;
}

Results

counter() = 1
counter() = 2
outside start = 0

Example 5: Lambda with STL Algorithms

Lambda is most commonly paired with STL algorithms for operations like sorting, searching, counting, and transforming, where you can directly write the local logic right at the call site.

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // vector 是动态数组,元素数量可以在运行时变化。
    std::vector<std::string> names = {"Bob", "Alice", "Charlie", "David"};

    std::sort(names.begin(), names.end(),
              [](const std::string& a, const std::string& b) {
                  if (a.size() == b.size())
                  {
                      return a < b;
                  }
                  return a.size() < b.size();
              });

    std::cout << "sort by length: ";
    for (const auto& name : names)
    {
        std::cout << name << " ";
    }
    std::cout << "\n";

    int min_length = 6;
    auto it = std::find_if(names.begin(), names.end(),
                           [min_length](const std::string& name) {
                               return name.size() >= static_cast<std::size_t>(min_length);
                           });

    if (it != names.end())
    {
        std::cout << "first long name = " << *it << "\n";
    }

    return 0;
}

Results

sort by length: Bob Alice David Charlie
first long name = Charlie

Example 6: Be mindful of lifecycle capture when saving callbacks

If a lambda is invoked immediately, reference capture usually seems fine; if stored in a container, thread, timer, or asynchronous callback, the lambda might execute after the local variables have gone out of scope. When saving a callback, prefer capturing the necessary data by value.

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

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // std::function 可以保存普通函数、lambda 或函数对象。
    // vector 是动态数组,元素数量可以在运行时变化。
    std::vector<std::function<void()>> callbacks;

    {
        std::string name = "Alice";
        int score = 95;

        auto print_now = [&name, &score]() {
            std::cout << "now: " << name << " " << score << "\n";
        };
        print_now();

        callbacks.push_back([name, score]() {
            std::cout << "saved: " << name << " " << score << "\n";
        });
    }

    for (const auto& callback : callbacks)
    {
        callback();
    }

    return 0;
}

Results

now: Alice 95
saved: Alice 95

This is called immediately, so capture by reference is fine. The lambda saved to callbacks uses capture by value, because name and score are already destroyed after leaving the inner scope.

Key Grammar Explanation

|Here is the translation of the provided Simplified Chinese Markdown fragment into natural American English, following all specified rules.


ExampleKey pointsExplanation
Example 1Comparison of old callback writing methodsRegular functions and function objects can both serve as callbacks, but lambda is better suited for short local logic.
Example 2Parameters and return valuesThe return type is usually inferred, but you need to explicitly specify it when different branches return different types.
Example 3capture list[x] copy, [&x] reference
Example 4mutableModifying the internal copy of the lambda does not affect the external variable.
Example 5STL algorithmssort, find_if, and count_if often work with lambda.
Example 6lifecycleIn save callbacks, asynchronous callbacks, and thread callbacks, do not arbitrarily reference captured local variables.

Common Errors

  1. When using default reference capture in callbacks [&], local variables are still accessed after they are destroyed.
  2. It's thought that by-value captures follow external variable changes. By-value captures save a copy at the time the lambda is defined.
  3. Intending to modify the copy captured by value, but forgot to add mutable.
  4. Using a lambda with captures as a function pointer. Lambdas with captures need to be stored using template parameters, auto variables, or std::function.
  5. Capturing this in an asynchronous scenario, the object is destroyed first. Need to ensure the object's lifetime, or use more explicit methods such as smart pointers and [*this].

使用建议

  • 明确目标:在开始前确定您的具体需求,以便选择最合适的工具或教程。
  • 充分利用资源:参考官方文档、教程和博客,这些资料能帮助您快速上手并解决问题。
  • 实践应用:通过动手操作项目或编写代码来巩固学习成果,提升实际操作能力。
  • 问题解决:遇到困难时,查阅参考资料或寻求社区支持,逐步培养独立解决问题的能力。
  • 分享经验:完成项目后,可以撰写文章或博客分享心得,帮助其他学习者。

如果需要针对特定领域(如单片机、机器人或环境搭建)的进一步建议,请提供更多信息,我将为您细化内容。

  1. For small, localized logic, prefer lambda.
  2. Try to write capture lists explicitly using [x, &y], and avoid relying on defaults [=] and [&] as much as possible.
  3. Prioritize capturing necessary data by value in callbacks, threads, timers, and asynchronous operations.
  4. For callbacks that are called only once and don't need to be saved, you can pass a lambda directly to the algorithm or function template.
  5. When you need to uniformly save different lambdas, use the std::function from the next section.

Summary

  • Lambdas are anonymous callable objects, written as [捕获](参数) { 函数体 }.
  • The capture list determines how the lambda uses external variables.
  • [x] is capture by value, and [&x] is capture by reference.
  • mutable allows modification of the internal copy of values captured by value.
  • Lambdas are most commonly used in STL algorithms, callbacks, and asynchronous tasks.
  • The lifecycle is the most error-prone aspect of Lambda, especially when dealing with saved callbacks and async callbacks.
音乐页