C++11/14 lambda表达式使用及解析

0. 引言

最近和同事讨论了一下C++中lambda函数的使用,因此想着周末总结一下lambda相关知识。

1. lambda的定义(lambda是什么?)

1.1 lambda表达式的定义

lambda函数的背景可以看看相关链接 C++里为什么要添加lambda?是C++本身的什么问题造成的? - 知乎 可能非官方。

下面看一下C++ Primer 给出的lambda表达式的定义

一个lambda表达式表示一个可调用的代码单元。一个表达式具有如下形式

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

譬如我们可以定义一个lambda表达式如下:

auto f = [&]() -> int {return 42;};

1.2 lambda 实现原理

lambda本身是一个函数对象,当编写好lambda表达式后,编译器会将该表达式翻译成一个未命名的类的未命名对象, 且在lambda表达式产生的类中含有一个重载的函数调用运算符。

上面文字如果看起来累,可以看下面一个例子来加深理解。我们定一个lambda表达式如下

auto f = [] (int x) {return 42;};

通过C++ Insights观察到上述代码生成如下的类定义:

 class __lambda_6_11
  {
    public: 
    inline /*constexpr */ int operator()(int x) const
    {
      return 42;
    }
    
    using retType_6_11 = int (*)(int);
    inline /*constexpr */ operator retType_6_11 () const noexcept
    {
      return __invoke;
    };
    
    private: 
    static inline int __invoke(int x)
    {
      return 42;
    }
    
    
  };

2. lambda使用总结

lambda函数有两种默认捕获方式:按引用和按值捕获。本文总结一下按引用捕获和按值捕获的缺点,以及给出一个我碰到的很骚气的lambda表示式与pthread_create函数结合的用法并分析为啥可以这样用。

2.1 lambda默认捕获方式

lambda表达式按引用捕获方式使用方法如下:

#include <iostream>

int main() {

    int b = 3;
    int c = 4;
    auto f = [&b] (int x) {return 42;};
    return 0;
}

引用捕获一般建议如上使用,在捕获列表中利用 &(捕获的变量名)表示按引用捕获。

lambda表达式按值捕获方式使用如下:

int c = 4;
auto fb = [c] () {return 42 - c;};

2.2 lambda表达式生成的对象的大小探究

首先需要明白,lambda表达式必然会将捕获的值存放在某个地方,个人无聊研究了一下在我机器上lambda表达式占用的内存

我们从下面的例子开始

    int a = 0;
    int b = 0;
    auto fb = [a, b]() {return 42;};
    std::cout << sizeof(fb) << std::endl;

分别进行如下三种测试:

    auto fb = [a, b]() {return 42;};
    auto fb1 = [&a, b]() {return 42;};
    auto fb2 = [&a, &b] () {return 42;};

其测试结果如下:

按值捕获所占内存大小:8
按值和引用捕获所占内存大小:16
按引用捕获所占内存大小:16
空捕获所占内存大小:1

因此可以看出,按引用捕获会导致lambda表达式占用内存变大,而空捕获列表的lambda表达式只会占用一个字节

2.3 按默认捕获方式的缺点

按引用捕获方式会导致程序产生未定义行为,当引用对象的生命周期比lambda表达式的生命周期短的时候便可能发生。其例子如下

void func(std::function<int()>& fb) {
    std::string a = "hello world";
    fb = [&a]() {
        std::cout << a << std::endl;
        return 42;
    };
}

int main() {
    std::function<int()> fb;
    func(fb);
    fb();
    return 0;
}

2.4 按值捕获

无论是按引用捕获还是按值捕获,均只能捕获局部变量(初静态变量和全局变量), 按值捕获的一个问题是,当我们想捕获一个类的成员变量时,会出现失败。


class A {
public:
    A() : a(0) {}

    void func() {
        int b = 0;
        auto fc = [b]{
            std::cout << "捕获a:" << b;
            return;};
        fc();
    }
private:
    int a;
};

int main() {

     A b;
     b.func();
     
    return 0;
}

 该程序会输出正确结果,按值捕获b。当我们试图捕获a时,也即lambda表达式变成

 auto fc = [a]{
            std::cout << "捕获a:" << a;
            return;};

此时编译器会报错:

error: 'a' in capture list does not name a variable

error: 'this' cannot be implicitly captured in this context

因此此时我们其实需要捕获this指针,也即lambda表达式变成

  auto fc = [this]{
            std::cout << "捕获a:" << a;
            return;};

此刻便会正确捕获类成员变量。

3. lambda与pthread_create的例子

看下面的实现

class ThreadImpl {
public:
    ThreadImpl(std::function<void()> thread_routinue);
    int join();
    pthread_t threadId();

private:
    std::function<void()> _thread_routinue;
    pthread_t _thread_handler;
};
ThreadImpl::ThreadImpl(std::function<void()> thread_routinue)
        : _thread_routinue(std::move(thread_routinue)) {
    auto rc =
            pthread_create(
                    &_thread_handler,
                    nullptr,
                    [] (void *arg) -> void* {
                       static_cast<ThreadImpl*>(arg)->_thread_routinue();
                        return (void*)1;},
                    this);

    if (rc != 0) {
        std::cout << "creat pthread error" << std::endl;
    }
}

在ThreadImpl的构造函数中 调用pthread_create传入了四个参数,其中第三个参数为一个lambda表达式,第四个参数为this指针。

上述表达式可以正确工作。原因其实是 lambda表达式会实例化成一个函数,然后作为第三个参数。

4. 总结

lambda表达式其实就是一个类模版,在使用时会被实例化。而且C++14中lambda表达式的使用越来越多。

本文会形成一个系列,具体讲解C++11/14/17/20中lambda表达式的高级特性和相关概念。目前已有系列二可参考

C++11/14 lambda表达式使用及解析(二)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qls315

感觉好可打赏几毛钱增强更新动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值