avatar

Catalog
Effective Modern C++ 笔记(6):Lambda Expressions

Chapter 6 Lambda Expressions

首先要搞清楚几个概念:

  • lambda 表达式:就是一个表达式,是代码本身:

    c++
    1
    [](int val) { return 0 < val && val < 10; }
  • closure:由 lambda 表达式创建出来的运行时对象,closure 包含 capture list 中的数据。

  • closure class:closure 对象所属的类。每有一个 lambda 表达式,编译器就会生成一个 closure class。lambda 表达式中的语句就出现在 closure class 的成员函数中。

注意,lambda 表达式和 closure class 存在于编译时刻,closure 存在于运行时刻。

Item 31 Avoid default capture modes

  • 使用默认的 by-reference capture 容易导致悬空引用的问题,除非能保证被捕获的变量生命周期不短于 lambda。指定 capture 变量至少能让问题变得更容易解决一些。

  • 使用 by-value capture 也会有悬空指针的问题(使用默认的 capture mode 将使这个问题更加隐蔽),尤其是 capture this 指针的时候,一个解决方法是将变量拷贝一份,然后 capture by value:

    c++
    1
    2
    3
    4
    5
    6
    7
    void Widget::addFilter() const {
    auto divisorCopy = divisor; // divisor is a data member
    filters.emplace_back(
    [divisorCopy](int value) // capture the copy
    { return value % divisorCopy == 0; } // use the copy
    );
    }

    C++14 中还可以使用 init capture 使代码更简洁:

    c++
    1
    2
    3
    4
    5
    6
    void Widget::addFilter() const {
    filters.emplace_back(
    [divisor = divisor](int value) // copy divisor to closure
    { return value % divisor == 0; } // use the copy
    );
    }

Item 32 Use init capture to move objects into closures

  • C++14 引入的 init capture 使 lambda 表达式更加强大灵活。使用 init capture,在 = 左边指定 closure class 中成员变量的变量名,在 = 右边指定由于初始化该成员变量的表达式。

  • init capture 可以用来移动构造 capture 变量,在 C++11 中也有方法来模拟这一点:用对象来移动构造一个 bind object(std::bind 生成的对象),然后将该对象引用传参给 lambda:

    c++
    1
    2
    3
    4
    auto func = std::bind(
    [](const std::vector<double>& data) {},
    std::move(data)
    );

    std::bind 的第二个参数是右值,所以被用来移动构造 bind object(func),当 func 被调用时,其中的成员变量被传递给 std::bind 的第一个参数,由于是引用传递,所以总的开销也就只有一次 move,和使用 init capture 一样。

Item 33 Use decltype on auto&& parameters to std::forward them

  • C++14 还引入了 generic lambda,即 labmda 参数类型可以是 auto,这个机制可以用来实现 lambda 的完美转发:

    c++
    1
    2
    3
    auto fwd = [](auto&& param) {
    f(std::forward<decltype(param)>(param));
    };
  • 和函数模板实现的完美转发相比,区别主要在于 forward 的类型参数:

    c++
    1
    2
    3
    4
    template<typename T>
    void fwd(T&& param) {
    f(std::forward<T>(param));
    }

    int 为例,当入参为左值时,前者 paramint&,后者 T 也是 int&forward 表现相同。但是当入参为右值时,情况有所不同:前者 paramint&&,后者 Tint,但是仔细看一下 forward 的实现就会发现,由于 reference collapsing,两者最终的表现仍然是相同的:

    c++
    1
    2
    3
    4
    template<typename T> 
    T&& forward(remove_reference_t<T>& param) {
    return static_cast<T&&>(param);
    }

Item 34 Prefer lambdas to std::bind

  • std::bind 相比,lambda 表达式在诸多方面要更加好用,例如延迟求值,很好处理重载函数,内联,可读性更高等等。
  • C++14 中没有理由再使用 std::bind 而不用 lambda 表达式了,因为它解决了 C++11 中 lambda 表达式两个小问题:
    • move capture,C++14 引入 init capture 可以移动构造 capture 变量。
    • polymorphic function objects,本质是 bind 的第一个参数是 callable,这个 callable 可以是个函数模板,而 C++14 引入 lambda 表达式的 auto 参数,从而也支持了这一点。

Reference

Author: Gusabary
Link: http://gusabary.cn/2020/05/25/Effective-Modern-C++-Notes/Effective-Modern-C++-Notes(6)-Lambda-Expressions/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.

Comment