技术: C++11 Lambda 探究

你知道的, 像函数指针, 函数对象(functor)这类都是非常方便的用于回调, 由于解耦的方式;
那么为什么还要弄一个lambda?

因为这是 C++, 这理由充分不?

引子

函数指针, 灵活, 但是危险, 因为没有类型安全检查, 或者你可以按照自己的意愿进行强转.
函数对象, 或者仿函数, 个人觉得麻烦, 虽然我可以按照面向对象的方式传递一个对象, 但是我为之付出的代价是 operator()() 以及定义一个类.

其实我只是想传递一个代码块儿, 在预定的条件触发下, 做一些我指定的操作, 那为什么不弄个匿名函数呢? lambda现身了, 好听的 函数式编程 来了.

最初体验lambda表达式时在 groovy 这门 domain specific language 里, 不过它语法太过简单(没有太多强制书写rules), 所以可读性差(特别是代码复杂的时候).

然后到了 c++ 这里, 其实也差不多, 也就这个样了, 来看看 C++ 中的语法和使用吧.

正文

闭包

直接上代码: (简单的案例代码)
(注意编译方式 g++ -std=c++11 -g -Wall main.cpp -o main)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <algorithm>
#include <functional>
#include <vector>



int main(void)
{
std::vector<int> v{1,2,3,4,5,6};
int x = 5;


//std::vector<int>::iterator
auto new_end = std::remove_if(v.begin(), v.end(),
[x](int n) { return n < x; });
auto old_end = v.end();

v.erase(new_end, old_end);

std::cout << "v: ";
for (auto i: v) {
std::cout << i << ' ';
}
std::cout << std::endl;


return 0;
}

看到啦

1
2
3
[x](int n){
return n<x;
}

这段匿名 闭包(closure) 替代了原来的 一元谓词函数 .

命名

lambda表达式其实就是一段匿名的闭包逻辑, 也可以称为匿名函数.
但是它能不能被命名呢? 可以, 如果你要多次使用这样的闭包逻辑的话(但是这是愚蠢的, 如果你要名字, 可以直接写一个函数呀)

  • 方法1 : auto
    使用 auto 自行推断多好.

    1
    2
    auto func = [](int i) { return ++i; };
    std::cout << "func: " << func(6) << std::endl;
  • 方法2 : std::function
    利用可调用函数对象的形式绑定

    1
    2
    std::function<int(int)> func = [](int i) { return ++i; };
    std::cout << "func: " << func(6) << std::endl;

果然还是觉得很傻, 劝你不要这样, 除非吧, 你要用 std::function 统一调用的形式.

详细语法

详细语法你可以参考 cppreference手册, 我这里简单的说吧.

完整的语法:

1
[ capture ] ( params ) mutable exception attribute -> ret { body }

但是其中很多部分都是可以省略的, 就像我喜欢省略返回值类型一样:

1
2
3
[ capture ] ( params ) -> ret { body }   //省略限定
[ capture ] ( params ) { body } //省略返回值类型
[ capture ] { body } //参数,返回值都省略

劝你还是写完整的吧.

详细说明:

  • 省略限定时, 该类型的表达式不能改捕获 “capture” 列表中的值; 相当于一个const的闭包(默认就是不改变捕获对象的, 因为捕获对象属于外部域)
  • 省略返回值时, 按照如下规则推断:
    • 如果包含了 return 语句, 则该 lambda 表达式的返回类型由 return 语句的返回类型确定
    • 如果没有 return 语句,则类似 void f(…) 函数
  • 省略了参数列表,类似于无参函数 f()

限定列表的说明:

  • mutable 修饰符说明 lambda 表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获对象的 non-const 方法
  • exception 说明 lambda 表达式是否抛出异常(noexcept),以及抛出何种异常,类似于void f() throw(X, Y)
  • attribute 用来声明属性

capture列表说明: (就相当于外部传递给内部的实参)

  • [a,&b] a变量以值的方式呗捕获,b以引用的方式被捕获
  • [this] 以值的方式捕获 this 指针
  • [&] 以引用的方式捕获所有的外部自动变量
  • [=] 以值的方式捕获所有的外部自动变量
  • [] 不捕获外部的任何变量

params 指定 lambda 表达式的参数, 就相当于形参呀.

尾巴

其实 lambda 表达式本身没有多少内容, 但是带来的改变却很大.
然而 C++ 做同一件事儿的方式从来都是多种多样的, 就看你怎么选了.

文章目录
  1. 1. 引子
  2. 2. 正文
    1. 2.1. 闭包
    2. 2.2. 命名
    3. 2.3. 详细语法
  3. 3. 尾巴
|