一文看懂C++ lambda表达式

本文详细介绍了C++11引入的lambda表达式的语法,包括参数列表、返回类型、捕获列表的使用。特别讨论了捕获方式,如值捕获、引用捕获、全值捕获和全引用捕获,以及C++14后的变化。还提到了在类方法中捕获this指针的情况,以及lambda表达式可能产生的空悬引用问题和mutable关键字的作用。最后,文章探讨了lambda表达式的本质,即它们是如何转换为函数对象的。
摘要由CSDN通过智能技术生成

原文链接:

前言

C++11后,lambda表达式被引入,它常常用来创建临时的匿名可调用对象,以简化代码编写。

本文主要从两个方面来剖析lambda表达式:

  • lambda语法
  • lambda底层实现
lambda语法

lambda的语法形式如下:

[capture list](parameter list) -> return type {function body}

其中,参数列表和返回类型是可以省略的,但必须永远包含捕获列表和函数体。

一些例子:

auto f = [] {return 42;}
[](const string& lhs, const string& rhs) {return a.size() < b.size();}
[a](int b) -> bool {return b < a;}

lambda表达式可以被作为一个变量来保存,此变量的类型只能用auto声明。

C++14之后,lambda的参数列表部分可以用auto声明,形参类型将被自动推导:

[](const auto& value) {return value % 3 == 0;}

C++14之前,仅当lambda表达式无返回语句或只有一条返回语句时,其返回值才可被自动推导。

C++14之后,只要返回类型兼容即可自动推导。

捕获后的变量才能在lambda函数体中使用,而能捕获变量也是lambda和普通函数的一大区别,众所周知,普通函数只能使用来自于参数或全局定义的变量,而lambda表达式可以在声明时捕获局部变量,以供lambda函数体使用。

那么,lambda如何捕获变量?有哪些不同的捕获方式呢?

捕获形式

捕获能且只能应用于当前作用域中的非静态局部变量(包括形参),外部的静态存储期变量虽然不能被捕获,但可以被直接使用。见例:

int a = 1;

void func() {
    static int b = 2;
    int c = 3;
    auto f = [/*capture list*/]() { // 只能捕获自动存储期的局部变量c,而不能捕获静态存储期的变量a,b
        // use a variable // a, b不经捕获也能使用
    };
    f();
}

捕获可以分为两种:

  • 值捕获,将被捕获变量的值拷贝到lambda中
  • 引用捕获,在lambda中保存被捕获变量的引用(指针)

另外还有隐式捕获,即单独的&,=声明而不指定变量名,那么lambda函数体中用到的未显式捕获的变量将以被隐式地(引用/值)捕获。

捕获列表的形式汇总:

  • 默认捕获:[],什么也不捕获

  • 值捕获:[varname],如[a],按值捕获被声明的变量

  • 引用捕获:[&varname],如[&a],按引用捕获被声明的变量

  • 全值捕获:[=],隐式按值捕获所有用到的变量

  • 全引用捕获:[&],隐式按引用捕获所有用到的变量

  • 混合使用:[&, varname1…],如[&, a, b],出现在列表中的变量被值捕获,而隐式捕获的变量被引用捕获。

  • 混合使用:[=, &varname1…],如[=, &a, &b],出现在列表中的变量被引用捕获,而隐式捕获的变量被值捕获。

注意=只在全值捕获或混合捕获中单独出现,而不能直接修饰某个被捕获变量,换言之,一个不被&修饰的变量默认就是值捕获的

关于this指针的捕获

在一个类的方法中定义的lambda表达式不能直接捕获字段,因为字段不是当前作用于内的局部变量,但是可以捕获this指针,然后就可以使用类中的字段:

class hello
{
public:
    void func()
    {
        auto f = [this]()
        {
            // d其实是this->d
            cout << d << endl;
        };
        f();
    }

private:
    int d = 4;
};

C++20之后不允许this指针的隐式捕获,即C++20之前,要捕获this指针,可以使用以下写法:

[=]{};
[this]{};

C++20之后,只允许下面的写法:

[=, this]{};
[this]{};
空悬问题

使用引用捕获时,lambda表达式将包含指涉到局部变量的引用,那么当lambda表达式的生命期超过该局部变量的生命期时,此lambda表达式将有一个空悬的无效引用,见下面这个致命的例子:

std::vector<std::function<bool<int>>;
void addFilter() {
    auto divisor = getDivisor();
    filters.emplace_back([&divisor](int value) {
       return value % divisor == 0;
    });
	// 当前函数返回后,divisor的值将失效,后续对该filter的使用将出错!
}

捕获this指针时也会有空悬问题,当lambda表达式作用于超过了当前对象的生存期后,this指针就会空悬。

mutable

lambda表达式按值捕获的变量默认都是不可修改的(当然,按引用捕获或者静态存储期变量可以修改),可以理解作它们被作为const字段保存在lambda实例中。见下面的例子:

int a = 1;
void func() {
    int b = 2;
    int c = 3;
    auto f = [b, &c] {
        a = 10; // OK
        // b = 20; // Compile Error: assignment of read-only variable 'b'
        c = 30; // OK
    };
    cout << a << " " << b << " " << c << endl;
}

输出如下:

10 2 30

要在lambda函数体中修改按值捕获的变量,就要加上关键字mutable:

auto f = [b, &c] () mutable {
        a = 10; // OK
        b = 20; // OK
        c = 30; // OK
};

输出仍是10 2 30,这是因为即使mutable令b可修改,但修改的是lambda中的副本b(修改结果只在lambda函数体中可见),所以原来的局部变量b的值不受影响。

广义捕获

广义捕获又叫初始化捕获,自C++14引入,它为移动捕获提供了直接支持(之前的移动捕获需要借助std::bind笨拙实现)。

广义捕获形式非常简单,就是在捕获列表中定义lambda中的新变量,例如:

auto pw = std::make_unique<Widget>();
[p = std::move(pw)] { // = 后面可以接任何合法的表达式,p变量将自动成为lambda内置的字段
    // do sth with p
};
本质

本质上,lambda是一个函数对象,大多编译器实现会把lambda的定义转换为一个类定义,被捕获的变量是类的字段,而lambda函数体就是类的operator()方法体。

举例,以下的lambda表达式:

size_t sz = getSz();
auto f = [sz](const string& a) {return a.size() >= sz;}

可能会被转换为以下类:

class f {
public:
    f(size_t n) : sz(n) {}
    bool operator()(const string& a) const {
        return s.size() >= sz;
    }
private:
    const size_t sz;
};

那么:

  • 按值捕获或按引用捕获的对应类字段的声明是值类型或引用类型。
  • 有无mutable修饰对应类字段和方法有无const修饰

原文链接:

引用

《C++Primer 5e》

《Effective Modern C++》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++ Lambda表达式是一种简洁的匿名表示方法,可以在代码中定义并使用。Lambda表达式的格式一般为:[] (参数列表) mutable -> 返回值类型 { 表达式 }。Lambda表达式可以捕获外部变量,并将其作为参数传递给函数体部分进行处理。Lambda表达式在使用时可以作为函数对象、函数指针或者函数参数进行传递。 Lambda表达式的底层原理是通过生成一个匿名类来实现。该类会重载函数调用运算符(),并包含Lambda表达式的函数体。Lambda表达式中捕获的外部变量会以成员变量的形式存储在该类中。当Lambda表达式被调用时,实际上是调用了该类的重载函数调用运算符()。 Lambda表达式可以与std::function结合使用,以实现函数对象的灵活使用。也可以将Lambda表达式赋值给相同类型的函数指针,实现函数指针的使用。但一般不建议这样使用,因为Lambda表达式已经提供了更加方便和简洁的方式。 总结来说,C++ Lambda表达式是一种用于定义匿名函数的语法,可以捕获外部变量并进行处理。其底层通过生成一个匿名类来实现,并提供了与std::function和函数指针的结合使用方式。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [C++11:lambda表达式](https://blog.csdn.net/zhang_si_hang/article/details/127117260)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值