掌握现代 C++:Lambda 在 C++14、C++17 和 C++20 中的演变
一、背景Lambda 是现代 C 最受欢迎的功能之一。自从在 C 11 中引入以来它们在 C 代码中无处不在。而且自从它们在 C11 中出现以来它们已经发展并获得了重要的功能。其中一些功能有助于编写更具表现力的代码并且由于现在使用 lambda 非常普遍因此花时间学习可以用它们做什么是非常值得的。本文目标是剖析 lambda 的主要演变过程但不是所有的小细节。对 lambda 的基础知识不了解可以阅读博主的另一篇文章有详细介绍。lambda 的演变一般是赋予它们手动定义函数对象的功能。二、C14 中的 Lambda在 C14 中lambda 获得了 4 项主要增强功能默认参数模板参数广义捕获从函数返回 lambda2.1、默认参数在 C14 中lambda 可以采用默认参数就像任何函数一样代码语言C自动换行AI代码解释auto myLambda [](int x, int y 0) { std::cout x - y \n; }; std::cout myLambda(1, 2) \n; std::cout myLambda(1) \n;输出代码语言Bash自动换行AI代码解释1-2 1-02.2、模板参数在 C11 中必须定义 lambda 参数的类型代码语言C自动换行AI代码解释auto myLambda [](int x){ std::cout x \n; };在 C14 中可以让它们接受任何类型代码语言C自动换行AI代码解释auto myLambda [](auto x){ std::cout x \n; };即使不需要处理多种类型这对于避免重复并使代码更紧凑和可读也很有用。例如这种 lambda代码语言C自动换行AI代码解释auto myLambda [](namespace1::namespace2::namespace3::ACertainTypeOfWidget const widget) { std::cout widget.value() \n; };变成代码语言C自动换行AI代码解释auto myLambda [](auto widget) { std::cout widget.value() \n; };2.3、广义捕获在 C11 中lambda 只能捕获其作用域中的现有对象代码语言C自动换行AI代码解释int z 42; auto myLambda [z](int x){ std::cout x - z 2 \n; };C14 借助强大的lambda广义捕获可以用几乎任何东西初始化捕获的值。示例代码语言C自动换行AI代码解释int z 42; auto myLambda [y z 2](int x) { std::cout x - y \n; }; myLambda(1);输出代码语言Bash自动换行AI代码解释1-442.4、从函数返回 lambdaLambda 受益于 C14 的语言功能从函数返回而无需指定返回类型。由于 lambda 的类型是由编译器生成的因此在 C11 中无法从函数返回 lambda。代码语言C自动换行AI代码解释/* what type should we write here ?? */ f() { return [](int x){ return x * 2; }; }在 C14 中可以通过用作返回类型来返回 lambda。这在一段代码中间有一个大的 lambda 的情况下很有用。展开代码语言C自动换行AI代码解释void f() { // ... int z 42; auto myLambda [z](int x) { // ... // ... // ... }; // ... }可以将 lambda 打包到另一个函数中从而引入另一个抽象级别展开代码语言C自动换行AI代码解释auto getMyLambda(int z) { return [z](int x) { // ... // ... // ... }; } void f() { // ... int z 42; auto myLambda getMyLambda(z); // ... }三、C17 中的 LambdaC17 为 lambda 带来了一个重大增强它们可以声明constexpr。代码语言C自动换行AI代码解释constexpr auto times2 [] (int n) { return n * 2; };然后可以在编译时评估的上下文中使用此类 lambda代码语言C自动换行AI代码解释static_assert(times2(3) 6);这在模板编程中特别有用。注意lambda 在 C20 中变得更加有用。事实上只有在 C20 中大多数 STL 算法才变得如此并且它们可以与 lambda 一起使用以创建在编译时评估的集合的复杂操作。不过有一个例外std::array非变异访问操作在 C 14 中立即变为std::array constexpr而在 C17 中变为非变异访问操作constexpr。lambda 在 C17 中获得的另一个特性是捕获*this的副本的简单语法。示例代码语言C自动换行AI代码解释struct MyType{ int m_value; auto getLambda() { return [this](){ return m_value; }; } };此 lambda 捕获this指针的副本。如果 lambda 的生存期超过对象的生存期则可能会导致内存错误例如代码语言C自动换行AI代码解释auto lambda MyType{42}.getLambda(); lambda();由于MyType在第一个语句的末尾被销毁因此第二个语句调用的lambda取消了this引用访问其m_value但this指向一个被销毁的对象。这会导致未定义的行为通常是应用程序崩溃。解决此问题是在lambda中捕获整个*this对象的副本。C17 提供了语法来实现这一点。展开代码语言C自动换行AI代码解释struct MyType { int m_value; auto getLambda() { return [*this](){ return m_value; }; } };当然在 C 14 中使用广义捕获已经可以实现相同的结果展开代码语言C自动换行AI代码解释struct MyType { int m_value; auto getLambda() { return [self *this](){ return self.m_value; }; } };只是C17 使语法更好。四、C20 中的 LambdaLambda 在 C 20 中得到进一步发展但其功能不如 C 或 C 17 那么基本。C 20 中 lambda 的一个增强功能是定义模板的经典语法使它们更接近手动定义的函数对象代码语言C自动换行AI代码解释auto myLambda []typename T(T value){ std::cout value \n; };这使得访问模板参数类型比使用 表达式如auto的 C lambda 模板更容易。另一个改进是能够捕获可变参数包代码语言C自动换行AI代码解释templatetypename... Ts void f(Ts... args) { auto myLambda [...args std::forwardTs(args)](){}; }五、总结lambda从C14到C20都有了不少的改进。但也还有更多没有总结进来。这些主要功能伴随着许多小特性让 lambda 代码更容易编写。深入研究 lambda 是更好了解 C 语言值得投入时间。