본문으로 건너뛰기 C++ Lambda Basics | Capture· mutable

C++ Lambda Basics | Capture· mutable

C++ Lambda Basics | Capture· mutable

Introduction: Sort without a functor class?

“One line of comparison—why a whole class?”

Before C++11, std::sort needed a functor with operator()—verbose for a one-liner. Lambda: a closure you define at the call site: capture -> ret { body }. Definition: A lambda is a nameless function object created where you need a callable—ideal for sort, find_if, thread entry points. Replace functor:

std::sort(people.begin(), people.end(),
    [](const Person& a, const Person& b) {
        return a.age < b.age;
    });

Table of contents

  1. Lambda syntax
  2. Capture modes
  3. mutable and noexcept
  4. Generic lambdas (C++14)
  5. Recursive lambdas
  6. Examples
  7. Common errors
  8. Best practices
  9. Production patterns
  10. Checklist

1. Basic syntax

// g++ -std=c++17 -o lambda_basic lambda_basic.cpp && ./lambda_basic
#include <iostream>
int main() {
    auto add = [](int a, int b) -> int {
        return a + b;
    };
    int result = add(3, 5);
    std::cout << result << "\n";
    return 0;
}

Return type can be omitted if deducible. IIFE (immediately invoked):

int result = [](int x) { return x * x; }(5);

2. Capture modes

  • []: nothing
  • [=]: copy all used automatic variables by value (default capture by value)
  • [&]: reference all used automatic variables
  • [x, &y]: mixed
  • [this]: capture this (C++17: *[this] copies the object) Thread/async: prefer by-value capture of what the async work needs—never reference to stack unless it definitely outlives the call.

3. mutable

By-value captures are const in operator() unless the lambda is mutable—then you can mutate the copies inside the closure, not the originals.

4. Generic lambdas (C++14)

auto print = [](auto value) {
    std::cout << value << "\n";
};

C++20: explicit template parameters on lambdas:

auto f = []<typename T>(T value) { /* ... */ };

5. Recursive lambdas

Use std::function and capture by reference, or Y-combinator style—beware value-capture of uninitialized std::function.

std::function<int(int)> factorial;
factorial = [&factorial](int n) -> int {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
};

6. Examples

Putting captures, mutable, and generic lambdas together in code you’d actually write:

#include <algorithm>
#include <vector>
#include <numeric>

std::vector<int> nums = {5, 3, 8, 1, 9, 2};

// Capture by reference to sort in place, generic (C++14) comparator
std::sort(nums.begin(), nums.end(), [](const auto& a, const auto& b) {
    return a < b;
});

// Capture by value + mutable: a stateful counter with its own copy of state
int threshold = 4;
auto count_above = [threshold, calls = 0](int x) mutable {
    ++calls;
    return x > threshold;
};
int above = std::count_if(nums.begin(), nums.end(), count_above);

// Capture by reference to accumulate into an outer variable
long total = 0;
std::for_each(nums.begin(), nums.end(), [&total](int x) { total += x; });

Each of these mirrors a real pattern: value capture for a self-contained predicate you might reuse or pass around, mutable capture when the lambda needs private per-call state without touching the outer scope, and reference capture when the whole point is to affect a variable outside the lambda.


7. Common errors

  1. Dangling reference: [&] to locals, lambda runs later → capture by value or extend lifetime
  2. Loop variable i: use [i] not [&i] in parallel work
  3. mutable needed to increment by-value members
  4. this / [*this] for async—object lifetime
  5. std::function + inline: type erasure can prevent inlining and allocate

8. Best practices

  • Prefer template parameters template<typename F> over std::function when you don’t need type erasure
  • Minimal capture lists—avoid [=]/[&] if you might add variables later unintentionally
  • noexcept when the lambda never throws (helps move/vector reallocation)

9. Production patterns

  • Background work: std::thread(data = std::move(data){ … });
  • ScopeGuard with lambdas for cleanup
  • Callbacks: value capture or shared_ptr for shared ownership

10. Checklist

  • Async/thread: value capture or proven lifetime
  • Loop index: by-value in loop lambdas
  • find/sort predicates: const& parameters for large objects

Keywords

C++ lambda, lambda expression, capture, mutable, generic lambda, sort, find_if, dangling reference

Summary

ItemSyntax
By-value default[=]
By-reference default[&]
Selective[x, &y]
this[this] / [*this] (C++17)
mutable[]() mutable { }
Principles: short local logic; watch lifetimes; prefer templates over std::function when possible.
Next: STL map and set #10-2
Previous: Variadic templates #9-3

FAQ

Lambda vs free function performance?

A. Lambdas passed as template arguments inline like free functions; std::function may heap-allocate and type-erase. One-line summary: Lambdas give local, optimizable callables for STL and concurrency—mind capture lifetimes.

References


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers C++, Lambda, lambda expression, capture, mutable, Generic Lambda.