基本表达式

[capture](parameters) -> return-type {body}

当没有返回类型时, 可以省略 -> return-type

变量捕获与lambda闭包实现

  • [] 不截取任何变量
  • [&] 截取外部作用域中所有变量,并作为引用在函数体中使用
  • [=] 截取外部作用域中所有变量,并拷贝一份在函数体中使用
  • [=, &foo] 截取外部作用域中所有变量,并拷贝一份在函数体中使用,但是对foo变量使用引用
  • [bar] 截取bar变量并且拷贝一份在函数体中使用,同时不截取其他变量
  • [x, &y] x按值传递,y按引用传递
  • [this] 截取当前类中的this指针。如果已经使用了&或者=就默认添加此选项。

lambda 的底层实现

lambda 其实是c++的语法糖,是通过c++编译器生成class来实现的,看一个简单的例子。

#include <functional>
using namespace std;

int main(int argc, char* argv[]) {
    int x = 0;
    int y = 0;
    int z = 0;
    auto func = [&](int a)->int{x+=a; y++; return x;};
    func(1);
  
    return 0;
}

通过cppinsights 站点来查看编译器的实现方法

#include <functional>
using namespace std;

int main(int argc, char** argv) {
    int x = 0;
    int y = 0;
    int z = 0;

    class __lambda_8_16 {
    public:
        inline /*constexpr */ int operator()(int a) const {
            x = x + a;
            y++;
            return x;
        }

    private:
        int& x;
        int& y;

    public:
        __lambda_8_16(int& _x, int& _y) : x{_x}, y{_y} {}
    };

    __lambda_8_16 func = __lambda_8_16{x, y};
    static_cast<const __lambda_8_16>(func).operator()(1);
    return 0;
}

匿名函数在编译器中生成了类 class __lambda_6_16,其中operator()中使用了x和y,参数是引用传参。

lambda 表达式中需要注意的事项

1. mutable 关键字

默认情况下,按值捕获的变量在lambda函数体内是const的,不能修改。使用mutable关键字可以移除这个const限制:

int x = 0;
auto func = [x]() mutable {
    x++;  // 可以修改,但修改的是副本
    return x;
};

2. 泛型lambda (C++14)

C++14引入了泛型lambda,可以使用auto作为参数类型:

auto add = [](auto a, auto b) { return a + b; };
std::cout << add(1, 2) << std::endl;      // 3
std::cout << add(1.5, 2.5) << std::endl;  // 4.0

3. 捕获时初始化 (C++14)

可以在捕获列表中初始化变量,这对于捕获只能移动的类型特别有用:

std::unique_ptr<int> ptr = std::make_unique<int>(42);
auto func = [value = std::move(ptr)]() {
    return *value;
};

4. 模板lambda (C++20)

C++20引入了模板lambda,可以使用模板语法:

auto func = []<typename T>(T a, T b) { return a + b; };
std::cout << func(1, 2) << std::endl;

5. constexpr lambda (C++17)

C++17允许lambda在编译时求值:

constexpr auto square = [](int n) { return n * n; };
static_assert(square(5) == 25);

6. this捕获的变化 (C++20)

C++20中,[=]不再隐式捕获this,需要显式捕获:

class MyClass {
    int value = 42;
public:
    auto getFunc() {
        // C++20: 需要显式捕获this
        return [=, this]() { return value; };
        // 或者使用 [*this] 按值捕获整个对象
    }
};

7. 生命周期问题

捕获引用时需要特别注意生命周期,避免悬空引用:

std::function<int()> createFunc() {
    int x = 42;
    // 危险:x在函数返回后被销毁
    return [&x]() { return x; };
}

// 正确做法:按值捕获
std::function<int()> createFuncSafe() {
    int x = 42;
    return [x]() { return x; };
}

8. 性能考虑

  • lambda通常会被编译器内联,性能接近普通函数
  • 避免在性能关键路径中使用复杂的捕获
  • 小lambda适合作为算法参数,如std::sort的比较函数

9. 与std::bind的比较

优先使用lambda而不是std::bind,因为:

  • lambda语法更清晰
  • 编译器优化更好
  • 类型安全更强
  • C++14后lambda功能更强大

10. 转换为函数指针

无捕获的lambda可以隐式转换为函数指针:

void callFunc(int (*func)(int)) {
    std::cout << func(5) << std::endl;
}

int main() {
    auto lambda = [](int x) { return x * 2; };
    callFunc(lambda);  // 可以转换
}

11. lambda参数特性

lambda的参数语法与普通函数类似,但有一些特殊之处:

默认参数

lambda支持默认参数,但需要注意使用场景:

auto greet = [](const std::string& name = "World") {
    std::cout << "Hello, " << name << "!" << std::endl;
};
greet();           // Hello, World!
greet("Alice");    // Hello, Alice!

引用参数和const引用

可以像普通函数一样使用引用参数:

auto swapValues = [](int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
};

int x = 5, y = 10;
swapValues(x, y);  // x=10, y=5

可变参数模板 (C++14)

lambda支持可变参数模板:

auto printAll = [](auto&&... args) {
    (std::cout << ... << args) << std::endl;
};
printAll(1, " + ", 2, " = ", 3);  // 输出: 1 + 2 = 3

参数类型推导

C++14引入的泛型lambda可以自动推导参数类型:

// 自动推导参数类型
auto maxValue = [](auto a, auto b) {
    return a > b ? a : b;
};
std::cout << maxValue(3.14, 2.71) << std::endl;  // 3.14
std::cout << maxValue(5, 10) << std::endl;       // 10

参数包展开

可以在lambda体内展开参数包:

auto sumAll = [](auto... args) {
    return (args + ...);  // 折叠表达式
};
std::cout << sumAll(1, 2, 3, 4, 5) << std::endl;  // 15

noexcept规范

可以为lambda指定noexcept:

auto safeDivide = [](int a, int b) noexcept -> int {
    if (b == 0) return 0;  // 注意:noexcept函数中抛出异常会终止程序
    return a / b;
};

属性说明符 (C++11/14/17)

可以为lambda添加属性说明符:

// [[nodiscard]] 属性 (C++17)
auto createResource = []() [[nodiscard]] {
    return std::make_unique<int>(42);
};

// [[deprecated]] 属性
auto oldFunc = []() [[deprecated("Use newFunc instead")]] {
    return 42;
};

12. 递归lambda

lambda不能直接递归调用自己,需要使用std::function或Y组合子:

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

// 使用Y组合子(高级技巧)
auto y = [](auto f) {
    return [=](auto... args) {
        return f(f, args...);
    };
};
auto factorial2 = y([](auto self, int n) -> int {
    return n <= 1 ? 1 : n * self(self, n - 1);
});