windows C++-Lambda表达式(一)

随着时代的发展,许多为了便利开发者的特性被加入到开发语言中,这是一种趋势,意味着编程语言不再向机器而是向开发人员倾斜,但减轻开发人员的负担意味着额外的支出,效率、编译器的复杂性、潜在的问题等等都会越来越多,本质上,开发是一种工程,在整体不变的情况下,这是一个零和的博弈。

例如,在32位的时代,程序员们几乎可以将系统的资源使用能力发挥到极致,地址空间安排的可以非常紧凑,并有异常惊人的技巧性和灵活性,将一些不可思议的实现付诸现实,这背后的代价是需要非常优秀的程序员,有着非必常人的创造力,对编程语言非常精通,对各种技术信手拈来,这样的程序员并不多见;然而在64位的时候,由于几乎无限的地址空间和运算资源,导致上一个时代的非常多的技巧都无效了,大部分程序可以平A过去。

以前的程序员可能需要考虑很多的问题: 程序分配了多少内存?开辟多少线程?线程间的同步技巧有哪些?如何高效的使用内存?如何设计并实现高并发和低延迟的系统?如何将系统的资源使用率卡在90%又不至于引起用户的不适?这些技巧以前是入门级,现在则是精通级别了,这也许是一件好事,但学习和了解新技术,要关注新技术背后的实现。

但很多程序并不需要非常强的健壮性和安全性,在某种情况下,一个工具适应一个场景,也不是那么不可接受,这取决于具体的项目和实践活动,例如Lambda就是是一个新加入的特性。

Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包,注意和数学传统意义上的不同。

Lambda 表达式的各个部分

下面是一个简单的 Lambda,它作为第三个参数传递给 std::sort() 函数:

#include <algorithm>
#include <cmath>

void abssort(float* x, unsigned n) {
    std::sort(x, x + n,
        // Lambda expression begins
        [](float a, float b) {
            return (std::abs(a) < std::abs(b));
        } // end of lambda expression
    );
}

此图显示了 Lambda 语法的组成部分:

  • capture 子句,在 C++ 规范中也称为 Lambda 引导;
  • 参数列表(可选)。 也称为 Lambda 声明符;
  • mutable 规范(可选);
  • exception-specification(可选);
  • trailing-return-type(可选);
  • Lambda 体;
capture 子句

Lambda 可在其主体中引入新的变量(用 C++14),它还可以访问(或“捕获”)周边范围内的变量。 Lambda 以 capture 子句开头。 它指定捕获哪些变量,以及捕获是通过值还是通过引用进行的。 有与号 (&) 前缀的变量通过引用进行访问,没有该前缀的变量通过值进行访问。

空 capture 子句 [ ] 指示 lambda 表达式的主体不访问封闭范围中的变量。

可以使用默认捕获模式来指示如何捕获 Lambda 体中引用的任何外部变量:[&] 表示通过引用捕获引用的所有变量,而 [=] 表示通过值捕获它们。 可以使用默认捕获模式,然后为特定变量显式指定相反的模式。 例如,如果 lambda 体通过引用访问外部变量 total 并通过值访问外部变量 factor,则以下 capture 子句等效:

[&total, factor]
[factor, &total]
[&, factor]
[=, &total]

使用默认捕获时,只有 Lambda 体中提及的变量才会被捕获。

如果 capture 子句包含默认捕获 &,则该 capture 子句的捕获中没有任何标识符可采用 &identifier 形式。 同样,如果 capture 子句包含默认捕获 =,则该 capture 子句没有任何捕获可采用 =identifier 形式。 标识符或 this 在 capture 子句中出现的次数不能超过一次。 以下代码片段给出了一些示例:

struct S { void f(int i); };

void S::f(int i) {
    [&, i]{};      // OK
    [&, &i]{};     // ERROR: i preceded by & when & is the default
    [=, this]{};   // ERROR: this when = is the default
    [=, *this]{ }; // OK: captures this by value. See below.
    [i, i]{};      // ERROR: i repeated
}

捕获后跟省略号是一个包扩展,如以下可变参数模板示例中所示:

template<class... Args>
void f(Args... args) {
    auto x = [args...] { return g(args...); };
    x();
}

要在类成员函数体中使用 Lambda 表达式,请将 this 指针传递给 capture 子句,以提供对封闭类的成员函数和数据成员的访问权限。

Visual Studio 2017 版本 15.3 及更高版本(在 /std:c++17 模式及更高版本中可用):可以通过在 capture 子句中指定 *this 通过值捕获 this 指针。 通过值捕获会将整个闭包复制到调用 Lambda 的每个调用站点。 闭包是封装 Lambda 表达式的匿名函数对象。当 Lambda 在并行或异步操作中执行时,通过值捕获非常有用。 它在某些硬件体系结构(如 NUMA)上特别有用。

在使用 capture 子句时,建议你记住以下几点,尤其是使用采取多线程的 Lambda 时:

引用捕获可用于修改外部变量,而值捕获却不能实现此操作。 mutable 允许修改副本,而不能修改原始项。

引用捕获会反映外部变量的更新,而值捕获不会。

引用捕获引入生存期依赖项,而值捕获却没有生存期依赖项。 当 Lambda 以异步方式运行时,这一点尤其重要。 如果在异步 Lambda 中通过引用捕获局部变量,该局部变量将很容易在 Lambda 运行时消失。 代码可能会导致在运行时发生访问冲突。

通用捕获 (C++14)

在 C++14 中,可在 Capture 子句中引入并初始化新的变量,而无需使这些变量存在于 Lambda 函数的封闭范围内。 初始化可以任何任意表达式表示;且将从该表达式生成的类型推导新变量的类型。 借助此功能,你可以从周边范围捕获只移动的变量(例如 std::unique_ptr)并在 Lambda 中使用它们。

pNums = make_unique<vector<int>>(nums);
//...
      auto a = [ptr = move(pNums)]()
        {
           // use ptr
        };
参数列表

Lambda 既可以捕获变量,也可以接受输入参数。 参数列表(在标准语法中称为 Lambda 声明符)是可选的,它在大多数方面类似于函数的参数列表。 

auto y = [] (int first, int second)
{
    return first + second;
};

在 C++14 中,如果参数类型是泛型,则可以使用 auto 关键字作为类型说明符。 此关键字将告知编译器将函数调用运算符创建为模板。 参数列表中的每个 auto 实例等效于一个不同的类型参数。

auto y = [] (auto first, auto second)
{
    return first + second;
};

Lambda 表达式可以将另一个 Lambda 表达式作为其自变量。 

由于参数列表是可选的,因此在不将自变量传递到 Lambda 表达式,并且其 Lambda 声明符不包含 exception-specification、trailing-return-type 或 mutable 的情况下,可以省略空括号。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值