Lambda表达式是C++11引入的一项核心特性,它允许你在需要函数对象的地方就地定义一个匿名函数,极大提升了代码的简洁性和表达能力。本质上它是编译器自动生成的匿名类(闭包类型)的实例,重载了operator()。

  • 基本语法

[捕获列表](参数列表) mutable(可选) 异常说明(可选) -> 返回类型(可选) {

    函数体

}

auto f = []{ return 42; };        // 无参数,返回int

auto f2 = [](int a, int b) { return a + b; };

  • 捕获列表

捕获列表决定了 Lambda 体内可以访问外部作用域的哪些变量,以及如何访问

  1. 值捕获(by value)

int x = 10;

auto f = [x] { return x * 2; };   // 拷贝 x,只读(默认)

x = 20;                           // 不影响 Lambda 内部的副本

std::cout << f();                 // 输出 20。

值捕获的变量在 Lambda 定义时就被拷贝,之后修改外部变量不影响内部副本。

  1. 引用捕获(by reference)

int x = 10;

auto f = [&x] { return x * 2; };  // 引用捕获,访问外部变量

x = 20;

std::cout << f();                 // 输出 40

危险:Lambda 的生命周期不能超过被引用变量的生命周期,否则产生悬空引用。

  1. 隐式捕获

[=]:以值捕获所有使用的自动变量(包括 this)

[&]:以引用捕获所有使用的自动变量

int a = 1, b = 2;

auto f1 = [=] { return a + b; };   // 值捕获 a,b

auto f2 = [&] { return a + b; };   // 引用捕获 a,b

  1. 混合捕获与默认捕获

int x = 1, y = 2, z = 3;

auto f = [=, &y] {   // 默认值捕获,但 y 是引用捕获

    return x + y + z;

};

auto g = [&, x] {    // 默认引用捕获,但 x 是值捕获

    return x + y + z;

};

默认捕获必须写在前面,后面可以加个别的显式捕获,但不能重复捕获同一个变量。

  1. 初始化捕获(C++14,广义捕获)

允许在捕获列表中定义新的变量并初始化。

int x = 10;

auto f = [y = x + 1] {   // 将 x+1 的结果拷贝给 y

    return y;

};

// 移动语义:捕获只能移动的对象

auto p = std::make_unique<int>(42);

auto f2 = [ptr = std::move(p)] {   // p 被移动进 Lambda

    return *ptr;

};

  1. 捕获 this 与 *this

在非静态成员函数内,[this] 捕获当前对象的指针(引用捕获 this)。

[*this](C++17)按值捕获当前对象的副本。

struct S {

    int val = 10;

    auto get_lambda() {

        return [*this] { return val; };  // 拷贝整个对象,安全

    }

};

  • 参数列表与返回类型
  1. 参数列表

与普通函数相同,支持auto(泛型Lambda)

auto add = [](auto a, auto b) { return a + b; };

std::cout << add(1, 2) << add(1.5, 2.5);

2..返回类型推导

  • 如果函数体只有一条 return 语句,自动推导返回类型。
  • 否则推导为 void(除非显式指定返回类型)。

auto f1 = [](int i) { return i * 2; };        // 返回 int

auto f2 = [](int i) -> double { return i; };  // 强制返回 double

auto f3 = [](int i) {

    if (i < 0) return -1;      // 多个返回语句,类型必须一致

    else return 1;

};

  • Mutable关键字

默认情况下,Lambda 的 operator() 是 const 的,值捕获的变量不可修改。加上 mutable 后允许修改副本。

int x = 10;

auto f = [x]() mutable {

    x += 5;         // 修改的是 Lambda 内部的 x 副本

    return x;

};

std::cout << f();   // 15

std::cout << x;     // 10(外部 x 不变)

  • Lumbda的类型和存储

每个 Lambda 表达式都有独有的、匿名的类型(闭包类型),无法直接写出它的类型名。通常用 auto 或 std::function 存储。

  • 假设Lambda中捕获this需要注意什么?

答:生命周期管理:捕获this指针的Lambda表达式可能会在当前对象已经销毁的情况下被调用。这种情况下,访问被捕获的this指针可能导致未定义行为,因为被捕获的指针已经失效。

场景:Lambda 被存储或传递到对象生命周期之外执行。

class Button {

public:

    std::function<void()> onClick;

    void simulateClick() { if (onClick) onClick(); }

};

class Dialog {

    Button btn;

public:

    Dialog() {

        btn.onClick = [this]() {   // 危险!捕获 this

            this->onButtonClicked();

        };

    }

    void onButtonClicked() { /* ... */ }

    ~Dialog() { /* ... */ }

};

void test() {

    auto dlg = std::make_unique<Dialog>();

    // 将 dlg->btn 存储到全局或长期存在的容器中...

} // dlg 销毁,但 btn.onClick 仍持有指向已销毁 Dialog 的 this 指针

结果:稍后调用 btn.onClick() 时,通过悬垂 this 访问成员,导致崩溃或更隐蔽的错误。

解决方法(安全捕获this):

1. 确保 Lambda 生命周期 ≤ 对象生命周期

  • 只在对象内部使用 Lambda,不将其传递出去。
  • 例如:在成员函数内同步调用 Lambda,不存储到成员变量或全局容器。
  1. 使用 std::enable_shared_from_this 和 weak_ptr
  2. [*this] 按值捕获对象副本
  3. 使用 [this, some_var] 显式捕获并确保生命周期

更多推荐