下面给出一种「由外到内、由简到繁」的分层方法,帮助你把 C++ Lambda 函数“一眼看穿”。读完之后,再遇到形如

auto f = [capture](params) specifiers -> return_type { body };

的代码时,可以像读普通函数一样迅速拆解。


一、先把它当成“匿名 struct + operator()”

编译器看见 Lambda 时,会偷偷生成一个未命名的类(闭包类型),大致如下:

struct __lambda_123 {
    /* 捕获的成员变量 */ 
    auto operator()(params) const specifiers -> return_type { body }
};

有了这个映射,你就不会觉得 Lambda 是什么“黑魔法”——它就是把函数体搬到类里,再给你个对象而已。


二、4 个语法部件快速定位

  1. 捕获列表 [...]
    决定“把哪些外部变量搬进闭包对象”。

    • [] 什么都不捕获
    • [=] 全部变量值捕获(按值复制)
    • [&] 全部变量引用捕获
    • [x, &y] x 按值,y 按引用
    • [this] 捕获当前对象的指针
    • [val = expr] C++14 起支持“初始化捕获”,相当于成员变量 val(expr)
  2. 形参列表 (...)
    和普通函数一模一样,可省略(无参时)。

  3. 后置修饰 specifiers(可选)
    常见组合:

    • mutable 允许修改按值捕获的副本(默认 operator() 是 const)
    • constexpr / consteval
    • noexcept
    • -> return_type 尾返回类型,用于推导不了或想显式写时
    • C++20 起可加模板形参、约束、协程等。
  4. 函数体 { ... }
    内部代码可直接使用捕获的成员变量(名字与外部变量相同)。


三、一个例子逐层解剖

std::vector<int> v = {1,2,3,4};
int factor = 2;

std::for_each(v.begin(), v.end(),
              [&, factor](int& x) mutable noexcept {
                  x += factor++;
              });

拆解:

  1. 捕获 [&, factor]

    • & 表示其余变量(v 等)按引用捕获;
    • 单独列出 factor,强制按值捕获(即闭包内保存一个副本)。
      因此闭包里相当于有
    struct __lambda {
        int factor;           // 按值
        std::vector<int>& v;  // 按引用
    };
    
  2. 形参 (int& x)
    每次算法传入 vector 元素的引用。

  3. 修饰 mutable noexcept

    • mutable 允许修改 factor 副本;
    • noexcept 说明不会抛异常。
  4. 函数体
    x += factor++;
    factor 的副本累加到元素上,然后自增副本(不会影响外部 factor)。


四、常见疑问 5 连答

  1. 为什么 [=] 里修改变量,外部没变?
    因为闭包里存的是“副本”,除非用 mutable,否则连副本都改不了。

  2. 想捕获 move-only 类型(如 unique_ptr)怎么办?
    C++14 起用初始化捕获:[p = std::move(ptr)]{ ... }

  3. Lambda 能递归吗?
    C++11 不行;C++14 起 auto f = [](auto self, int n){ return n<2?1:n*self(self,n-1); };
    或把闭包对象先存到 std::function 里再递归。

  4. 如何返回另一个 Lambda?
    直接写即可,编译器会推导出闭包类型;若想类型擦除,用 std::function<...> 包装。

  5. 模板 Lambda 怎么用?
    C++20 起:[]<typename T>(T a, T b){ return a + b; }


五、记忆口诀(助记版)

捕参饰体,尾返可省;
按值只读,欲改加 mut;
初始捕获,move 无忧;
看作匿名类,万事不愁。


结语

把 Lambda 当成“语法糖形式的函数对象”,再按上面 4 个部件扫一眼,任何复杂 Lambda 都能迅速拆分。写代码时,先想清楚“我要捕获哪些变量、按值还是引用、是否需要 mutable”,随后填形参和函数体即可。

更多推荐