浅析C++11的Lambda表达式

Lambda表达式(又称Lambda函数,英文原文是Lambda Expression),是C++11的新特性中非常实用的一个。

之前一直自动忽略C++11的新特性,因为很多新特性的应用场景都十分有限,Lambda表达式就不同了,网上放狗一搜可以看到「函数对象」的理念在不少语言中都有实现,标准模板库之前有std::function来把函数包装成对象来使用,而现在有了Lambda表达式之后就更加方便了。

下面稍微解释一下Lambda表达式。

按照cppreference的官方中文解释,

构造一个闭包:能在作用域内捕获变量一个的匿名函数对象。

至于什么是闭包,可以看它在维基百科的词条。简而言之,就是一个匿名函数对象,它能捕获当前作用域的变量来运行。

下面不去扯计算机科学中生硬的概念了,从实用的角度出发,看看Lambda表达式是怎样帮助我们实现「code less」的。

一个常见的Lambda表达式的结构如下(省略了mutable、exception、attribute和-> ret):

[ 捕获 ] ( 参数列表 ) { 函数体 }

Lambda表达式的结果就是一个函数对象,因此可以在需要使用函数的地方用Lambda表达式来替代。除了[]是Lambda表达式特有的,后面的参数列表、函数体是和普通命名函数一样的。那捕获是什么意思该怎么写呢?

  • [&]表示以引用形式捕获当前作用域的全部自动储存持续性变量
  • [=]表示以传值形式捕获当前作用域的全部自动储存持续性变量
  • [a, &b]以传值形式捕获变量a,以引用形式捕获变量b
  • [a, &]以传值形式捕获变量a,以引用形式捕获当前作用域其它的自动储存持续性变量
  • [this]以传值形式捕获this指针
  • []什么也不捕获

所谓传值(value)还是引用(reference)和命名函数中一样,引用传递的参数在Lambda表达式的函数体中被修改后,原作用域的变量也会发生变化,而传值的变量则会被作为只读变量供Lambda表达式函数体来使用,Lambda表达式的函数体无法修改该变量(加上mutable关键词可以修改,但是修改的结果不会影响到原作用域)。下面以一个小例子来说明:

#include <iostream>
#include <functional>
#include <algorithm>

using namespace std;

int main ()
{
        int a = 10, b = 10;
        function<void (void)> func1 = [a] () mutable { cout << ++a << endl; };
        function<void (void)> func2 = [&b] () { cout << ++b << endl; };

        func1();
        func2();
        cout << "a: " << a << "\nb: " << b << endl;

        return 0;
}

使用GCC(g++)编译的时侯,记得加上-std=c++0x或者-std=c++11来启用C++11标准。运行后的结果如下:

11
11
a: 10
b: 11

可以看到,虽然有mutable关键词,在func1函数体内部也被修改了,但是在原作用域(即main中),a并没有被修改,因为a是通过传值捕获的变量。而b成功被修改了,因为b是引用捕获的。如果去掉mutable关键词,这段代码在编译时就会报错,因为默认传值捕获的变量是只读的(相当于常量),不可以被修改。

上面这个例子还顺便说明了如何把Lambda表达式存储为一个std::function对象,更为简便的办法是结合C++11中的auto标识符,上面的func1声明语句就可以改为:

auto func1 = [a] () mutable { cout << ++a << endl; };

顺便说一下,Lambda表达式最好用的地方在于和for_each、remove_if等结合使用,完全不需要事先定义好一个函数,在传递functor的地方直接写一个Lambda表达式就可以了,这样不仅少了常规命名函数的声明,而且更加安全,因为Lambda表达式相当于一个供for_each、remove_if等调用的临时(或者说匿名)函数对象。

C++11惊喜多多,赶紧升级你的编译器吧(至少需要Microsoft Visual Studio 2012和GCC 4.7)!

参考文章:std::function   |   Lambda函数

Comments

2 responses to “浅析C++11的Lambda表达式”

  1. 御宅暴君 avatar

    C++11 相对于 C++98, 可以说是一门全新的语言了,值得关注。

    1. librehat avatar

      现在我是越用越爽,C++11的语法糖很多,吃起来开心