C++ Lambda表达式捕获的作用、与参数传递的区别及适用场景
Great question—lambda captures are one of those C++ features that feel straightforward at first, but their purpose and how they differ from regular parameters can be confusing. Let’s break this down step by step.
捕获的核心作用
Put simply, captures let a lambda access variables from the scope where it’s defined (the "enclosing scope"), rather than only using values passed explicitly through its parameter list. Think of it as the lambda "grabbing" a reference or copy of variables that exist around it when it’s created, so it can use them later, even if those variables aren’t passed in when the lambda is called.
For example, if you have a local variable in a function and want your lambda to use it, you need to capture it—otherwise the lambda has no visibility into that variable:
#include <iostream> void print_count() { int count = 5; // Capture count to use it inside the lambda auto print = [count]() { std::cout << "Count is: " << count << std::endl; }; print(); // Outputs "Count is: 5" }
捕获 vs 直接传递参数:Key Differences
The two mechanisms serve similar goals (giving the lambda access to values) but work in fundamentally different ways:
- Source of scope: Parameters are passed to the lambda when it’s called, coming from the caller’s scope. Captured variables come from the scope where the lambda was defined, not where it’s invoked.
- Timing of value/access:
- For value captures (
[x]), the lambda gets a copy of the variable’s value at the moment the lambda is created. Changes to the original variable after the lambda is defined won’t affect this copy. - For reference captures (
[&x]), the lambda gets a reference to the original variable, so it uses the variable’s current value whenever the lambda is called. - Parameters always use the value/reference passed at the time of the lambda’s call.
- For value captures (
- Syntax & usage: Parameters are declared in the lambda’s parentheses
(), and you must pass values for them every time you call the lambda. Captures are declared in the lambda’s capture clause[], and you don’t need to pass anything related to them when calling the lambda.
Here’s a side-by-side example to highlight the difference:
#include <iostream> void compare_mechanisms() { int x = 10; // Value capture: uses x's value at lambda definition auto capture_lambda = [x]() { std::cout << "Capture value: " << x << std::endl; }; x = 20; capture_lambda(); // Outputs "Capture value: 10" // Parameter passing: uses x's value at lambda call auto param_lambda = [](int val) { std::cout << "Parameter value: " << val << std::endl; }; param_lambda(x); // Outputs "Parameter value: 20" }
When to Use Captures Instead of Direct Parameters
Captures shine in specific scenarios where passing parameters would be cumbersome or incorrect:
- Accessing multiple enclosing scope variables: If your lambda needs to use several variables from its surrounding scope (like class members, or multiple local variables), capturing them (e.g.,
[this]for class members,[a, b, c]for locals) is far cleaner than passing each one as a parameter every time you call the lambda. - Preserving state for delayed execution: If the lambda will be called later (e.g., as an asynchronous callback, a timer handler, or stored in a container), captures let you "freeze" the state of variables at the time the lambda is created. For example, if you launch an async task and want the callback to use a specific value from when the task started, a value capture ensures that happens.
- Simplifying short, scoped lambdas: In tight scopes like loops or small helper functions, capturing a loop variable or local value makes the code more readable—you don’t have to clutter the lambda’s parameter list with values that are already available in the surrounding code.
Example of a callback using captures to preserve state:
#include <future> #include <iostream> void async_example() { int task_id = 123; // Capture task_id to use in the async callback auto on_task_complete = [task_id]() { std::cout << "Task #" << task_id << " finished successfully!" << std::endl; }; // Launch async task (simulate work) std::async(std::launch::async, [](){ /* Do some long-running work */ }); // Even if we modify task_id later, the callback uses the captured value task_id = 456; on_task_complete(); // Outputs "Task #123 finished successfully!" }
内容的提问来源于stack exchange,提问作者dlo

