C++ lambda匿名函数

Lambda 表达式完整的格式如下:

[捕获列表] (形参列表) mutable 异常列表-> 返回类型
{
    函数体
}

各项的含义:

  • 捕获列表:捕获外部变量,捕获的变量可以在函数体中使用,可以省略,即不捕获外部变量。
  • 形参列表:和普通函数的形参列表一样。可省略,即无参数列表
  • mutable:mutable 关键字,如果有,则表示在函数体中可以修改捕获变量,根据具体需求决定是否需要省略。
  • 异常列表:noexcept / throw(...),和普通函数的异常列表一样,可省略,即代表可能抛出任何类型的异常。
  • 返回类型:和函数的返回类型一样。可省略,如省略,编译器将自动推导返回类型。
  • 函数体:代码实现。可省略,但是没意义。

示例:

void LambdaDemo()
{
    int a = 1;
    int b = 2;
    auto lambda = [a, b](int x, int y)mutable throw() -> bool
    {
        return a + b > x + y;
    };
    bool ret = lambda(3, 4);
}

对应的汇编代码如下:

LambdaDemo()::{lambda(int, int)#1}::operator()(int, int):
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     DWORD PTR [rbp-12], esi
        mov     DWORD PTR [rbp-16], edx
        mov     rax, QWORD PTR [rbp-8]
        mov     edx, DWORD PTR [rax]
        mov     rax, QWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rax+4]
        lea     ecx, [rdx+rax]
        mov     edx, DWORD PTR [rbp-12]
        mov     eax, DWORD PTR [rbp-16]
        add     eax, edx
        cmp     ecx, eax
        setg    al
        pop     rbp
        ret
LambdaDemo():
        push    rbp
        mov     rbp, rsp
        sub     rsp, 32
        mov     DWORD PTR [rbp-4], 1
        mov     DWORD PTR [rbp-8], 2
        mov     eax, DWORD PTR [rbp-4]
        mov     DWORD PTR [rbp-20], eax
        mov     eax, DWORD PTR [rbp-8]
        mov     DWORD PTR [rbp-16], eax
        lea     rax, [rbp-20]
        mov     edx, 4
        mov     esi, 3
        mov     rdi, rax
        call    LambdaDemo()::{lambda(int, int)#1}::operator()(int, int)
        mov     BYTE PTR [rbp-9], al
        nop
        leave
        ret

可见,在调用匿名函数时,直接调用了LambdaDemo()::{lambda(int, int)#1}::operator()方法,这也体现出lambda匿名函数的优势

  • 1、内联优化:Lambda表达式可以被编译器更好地内联优化。由于lambda通常在定义时使用,编译器可以获取更多的上下文信息,从而进行更有效的优化。(能够在需要使用的时候定义,并且无需跳出当前函数)

  • 2、减少函数调用的开销:相比于传统函数,匿名函数可以减少函数调用的开销,因为它们不需要通过函数指针调用,并且可以避免在调用栈上创建额外的帧。

  • 3、泛型编程:C++14引入了泛型lambda表达式,它允许编写更通用的代码,减少了模板特化的需要,这样不仅可以提高代码的可读性,还可以减少编译器生成的代码量,从而可能提高性能。

PS:函数调用汇编代码:
image
可以看出,在用函数调用来实现上述lambda匿名函数相同的功能时,因未能获取更多的上下文信息,就需要传入更多的参数来实现该功能,从而影响性能

可使用:https://godbolt.org/ 查看对应代码的汇编

编译器原理

编译器实现 lambda 表达式大致分为一下几个步骤

  • 1、创建 lambda 类,实现构造函数,使用 lambda 表达式的函数体重载 operator()(所以 lambda 表达式 也叫匿名函数对象)
  • 2、创建 lambda 对象
  • 3、通过对象调用 operator()
class lambda_xxxx
{
private:
    int a;
    int b;
public:
    lambda_xxxx(int _a, int _b) :a(_a), b(_b)
    {
    }
    bool operator()(int x, int y) throw()
    {
        return a + b > x + y;
    }
};
void LambdaDemo()
{
    int a = 1;
    int b = 2;
    lambda_xxxx lambda = lambda_xxxx(a, b);
    bool ret = lambda.operator()(3, 4);
}

其中,类名 lambda_xxxx 的 xxxx 是为了防止命名冲突加上的。
lambda_xxxx 与 lambda 表达式 的对应关系

  • lambda 表达式中的捕获列表,对应 lambda_xxxx 类的private 成员
  • lambda 表达式中的形参列表,对应 lambda_xxxx 类成员函数 operator() 的形参列表
  • lambda 表达式中的mutable,对应 lambda_xxxx 类 成员函数operator()的常属性 const,即是否是常成员函数
  • lambda 表达式中的返回类型,对应 lambda_xxxx 类成员函数 operator() 的返回类型
  • lambda 表达式中的函数体,对应 lambda_xxxx 类成员函数 operator() 的函数体

另外,lambda 表达 捕获列表的捕获方式,也影响 对应 lambda_xxxx 类的 private 成员 的类型

  • 值捕获:private 成员 的类型与捕获变量的类型一致
  • 引用捕获:private 成员 的类型是捕获变量的引用类型

注:
如果该匿名函数未用mutable关键词修饰,如下:

auto func1 = [](){cout << "hello world!" << endl; };
func1();

对应的类:(着重关注operator()的定义,属性)

template<typename T=void>
class TestLambda01
{
public:
    TestLambda01() {}
    void operator()()const
    {
        cout << "hello world!" << endl;
    }
};

operator()它是const的 哦^_ ^

posted @ 2024-01-26 23:53  牛犁heart  阅读(95)  评论(0编辑  收藏  举报