Lambda expression
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:
- Write an ordinary function.
- Write a function pointer.
- 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:
| Part | example | Meaning |
|---|---|---|
| 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
eventobject and sometimes acontextorcallback. - 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.
- JSON payload:
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:
constexprLambda,[*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 method | Meaning | Applicable Scenarios |
|---|---|---|
[] | Do not capture external variables. | Only use parameters or local temporary variables. |
[x] | Capture by value x | Save a copy, suitable for saving callbacks. |
[&x] | Capture by reference x | Need to modify external variables while ensuring their lifecycle. |
[x, &y] | Hybrid Capture | Clarify which are copies and which are references |
[=] | captures used variables by value by default | Small examples are convenient for learning, but avoid overusing them in actual projects. |
[&] | Capture used variables by default | Especially dangerous in asynchronous or saved callbacks. |
[this] | Capture the current object pointer | Objects must outlive lambdas. |
[*this] | Capture a copy of the current object | Available 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.
| Example | Key points | Explanation |
|---|---|---|
| Example 1 | Comparison of old callback writing methods | Regular functions and function objects can both serve as callbacks, but lambda is better suited for short local logic. |
| Example 2 | Parameters and return values | The return type is usually inferred, but you need to explicitly specify it when different branches return different types. |
| Example 3 | capture list | [x] copy, [&x] reference |
| Example 4 | mutable | Modifying the internal copy of the lambda does not affect the external variable. |
| Example 5 | STL algorithms | sort, find_if, and count_if often work with lambda. |
| Example 6 | lifecycle | In save callbacks, asynchronous callbacks, and thread callbacks, do not arbitrarily reference captured local variables. |
Common Errors
- When using default reference capture in callbacks
[&], local variables are still accessed after they are destroyed. - It's thought that by-value captures follow external variable changes. By-value captures save a copy at the time the lambda is defined.
- Intending to modify the copy captured by value, but forgot to add
mutable. - Using a lambda with captures as a function pointer. Lambdas with captures need to be stored using template parameters,
autovariables, orstd::function. - Capturing
thisin 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].
使用建议
- 明确目标:在开始前确定您的具体需求,以便选择最合适的工具或教程。
- 充分利用资源:参考官方文档、教程和博客,这些资料能帮助您快速上手并解决问题。
- 实践应用:通过动手操作项目或编写代码来巩固学习成果,提升实际操作能力。
- 问题解决:遇到困难时,查阅参考资料或寻求社区支持,逐步培养独立解决问题的能力。
- 分享经验:完成项目后,可以撰写文章或博客分享心得,帮助其他学习者。
如果需要针对特定领域(如单片机、机器人或环境搭建)的进一步建议,请提供更多信息,我将为您细化内容。
- For small, localized logic, prefer lambda.
- Try to write capture lists explicitly using
[x, &y], and avoid relying on defaults[=]and[&]as much as possible. - Prioritize capturing necessary data by value in callbacks, threads, timers, and asynchronous operations.
- 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.
- When you need to uniformly save different lambdas, use the
std::functionfrom 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.mutableallows 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.