lambda演进史(C++14/17/20)

译者注:本文翻译自:https://www.fluentcpp.com/2021/12/13/the-evolutions-of-lambdas-in-c14-c17-and-c20/

lambda表达式是现代C++中最流行功能之一,自从C++11被引进以来,在C++的代码中变得无处不在。同时lambda表达式也行进化获得重要的功能,这些功能能够帮助写出更强表现力的代码。进而因为lambda变得如此的通用,花一些时间学习如何使用它也是有价值的。

这里我们的目标是涵盖演进过程中的主要的点,没能包含所有的细节。对lambda表达式的全面的讲述更适合在一本书而不是一篇文章。如果你想了解更多的话,这里推荐Bartek的书 - C++ Lambda Story,里边会讲述所有的细节。

lambda的进化就是赋予它手动定义函数对象的能力

本篇文章假设你已经了解C++11中的基础知识,所以我们从C++14开始讨论

C++14的lambda

C++14中,lambda表达式主要4点主要的改进:

  • 默认参数
  • 模板参数
  • 广义捕获
  • 从函数中返回lambda

默认参数

在C++14中,lambda表达式可以使用默认参数,和函数类似:

auto myLambda = [](int x, int y = 0) {
    std::cout << x << '-' << y << '\n'; 
};

std::cout << myLambda(1, 2) << '\n';
std::cout << myLambda(1) << '\n';

输出为:

1-2
1-0

模板参数

C++11中我们必须定义lambda的参数类型:

auto myLambda = [](int x) { std::cout << x << '\n'; };

C++14中我们可以通过这些书写使得接收所有的类型参数:

auto myLambda = [](auto&& x) { std::cout << x << '\n'; };

即使你不需要处理多种类型的情况下,这中书写方式也十分有用,可以避免重复,且代码更加紧凑和可读,举例来说:

auto myLambda = [](namespace1::namespace2::namespace3::ACertainTypeOfWidget const& widget) { 
    std::cout << widget.value() << '\n'; 
};

这个可以使用以下写法来优化:

auto myLambda = [](auto&& widget) {
    std::cout << widget.value() << '\n'; 
};

译者注:书写更加简单

广义的捕获

C++11中lambda只能捕获作用域内存在的对象:

int z = 42;
auto myLambda = [z](int x){
    std::cout << x << '-' << z + 2 << '\n';
};

但是随着在C++14中有了强有力的广义的捕获能力,我们可以使用任何变量来初始化捕获值,以下是简单例子:

int z = 42;
auto myLambda = [y = z + 2](int x){
    std::cout << x << '-' << y << '\n';
};

myLambda(1);

输出为:

1-44

从函数中返回lambda

受益于C++14的语言特性:从函数返回auto,而不用指定返回值类型。因为lambda表达式的类型是编译器生成的,所以在C++11中我们不能从函数中返回一个lambda表达式:

/* what type should we write here ?? */ f()
{
    return [](int x){ return x * 2; };
}

译者注:因为lambda表达式的类型是编译器生成,C++11中不知道类型是什么,所以无法指定

在C++14中可以使用auto作为返回值类型,所以可能返回一个lambda表达式。这在一个表达式位于一大段代码中间的情况下很有用:

void f()
{
    // ...
    int z = 42;
    auto myLambda = [z](int x)
                    {
                        // ...
                        // ...
                        // ...
                    };
    // ...
}

我们可以包装lambda表达式在另外一个函数中,从而引入另一个抽象级别:

auto getMyLambda(int z)
{
    return [z](int x)
           {
               // ...
               // ...
               // ...
           };
}

void f()
{
    // ...
    int z = 42;
    auto myLambda = getMyLambda(z);
    // ...
}

如果想要了解更多这个知识点,请探索这个吸引人的话题:非正规的(out-of-line)lambda[https://www.fluentcpp.com/2020/06/05/out-of-line-lambdas/]

C++17的lambda

constexpr lambda

C++17的lambda有一个主要的优化:可以使用constexpr声明

constexpr auto times2 = [] (int n) { return n * 2; };

这样的lambda可以在编译期求值的上下文中使用:

static_assert(times2(3) == 6);

在模板编程中十分有用。

需要注意的是constexpr lambda在C++20中非常有用。事实上在C++20中std::vector和很多的STL算法都变成constexpr,与constexpr lambda一起在编译期能够完成复杂的计算。

有一个例外的容器std::array,在C++14中它的非变异(non-mutating)的访问就是constexpr,C++17中变异(mutating)访问操作也变成了constexpr了。

译者注:非变异操作指不会改变容器的内容的算法操作。变异操作刚好相反。

捕获*this的拷贝

C++17中另外一个lambda表达式的优化是用于捕获*this拷贝的简单语法,如下例子:

struct MyType{
    int m_value;
    auto getLambda()
    {
        return [this](){ return m_value; };
    }
};

这个lambda捕获了this指针的拷贝,如果lambda表达式的寿命比这个this对象长的话,就会有内存问题。结合如下代码:

auto lambda = MyType{42}.getLambda();
lambda();

第一条语句结束后MyType就被析构了,第二句调用lambda时会访问this的成员变量m_value,但是这个this指针被析构了,这就会导致被定义的行为了,典型的就是程序奔溃。

解决这个问题的可能方案是拷贝整个对象。C++17提供以下语法来完成(注意this前边有*):

struct MyType
{
    int m_value;
    auto getLambda()
    {
        return [*this](){ return m_value; };
    }
};

需要注意的是这个在C++14中已经可以通过广义捕获来实现相同的结果:

struct MyType
{
    int m_value;
    auto getLambda()
    {
        return [self = *this](){ return self.m_value; };
    }
};

C++17这里只是让语法更简单易用

C++20的lambda

发展到C++20,lambda的功能就不及C++14,C++17基础了。C++20的lambda表达式的一个优化是定义模板的经典语法,使得更急接近手动定义函数对象。

auto myLambda = [](T&& value){ 
    std::cout << value << '\n'; 
};

这使得访问模板参数类型比C++14使用诸如auto&&这样的表达式模板lambda更容易

译者注:声明了模板参数比auto&&这样更加灵活,比如说函数参数的类型是要对T进行萃取操作后的类型(eg.std::remove_cv), 除此之外使用模板定义还可以使用concepts。

C++20的lambda另外一个优化是可以捕获一组可变参数:

template
void f(Ts&&... args)
{
    auto myLambda = [...args = std::forward(args)](){};
}

深入lambda

我们已经讨论了我认为是lambda从C++14到C++20的主要优化,但远不止此。这些主要特性附带了一些小东西,使得lambda代码更容易编写。

深入研究lambda对于更好地理解C++语言是一个很好的机会,我认为这是值得的时间投资的,更进一步说,我所知道的最好的资源是Bartek的C++ Lambda Story书籍,强烈推荐。




个人微信公众号

0号程序员

你可能感兴趣的:(C++,c++,lambda)