- lambda表达式的值捕获或者引用捕获只能捕获lambda表达式的作用域内可见的非静态局部变量,包括形参;
- 在构造lambda表达式的时候,尽量不要使用默认的值捕获或者默认的引用捕获,而是直接将需要捕获的变量放到捕获列表中;
- 在类中使用lambda捕获成员变量时,无法通过将成员变量加入到捕获列表里面去捕获,可以通过默认的值捕获的方式捕获成员变量:
class A { private: int m; public: void f() { auto r = [](){std::cout << m;}; // 错误,没有捕获变量m auto r = [m](){std::cout << m;}; // 错误,无法将成员变量放到捕获列表中 auto r = [=](){std::cout << m;}; // 可以,但是不建议,全部变量通过值捕获,不过实际捕获的是this指针(得到this的副本),而不是变量m auto r = [&](){std::cout << m;}; // 可以,但是不建议 auto r = [s = m](){std::cout << s;}; // 正确,C++14中的初始化捕获模式(又叫广义捕获),使用m初始化闭包类中的成员变量s
- lambda中按引用捕获比较容易产生空悬引用(类似于空悬指针),按值捕获极易受空悬指针的影响,并且会误导人们认为此lambda表达式是自洽的(自洽是指与外部的环境是绝缘的,外部不管怎么变化,都不影响内部的状态);
- lambda表达式的初始化捕获与
类似,都是在创建表达式(或者说创建闭包或绑定对象的时候),就进行了初始化,会在闭包内部创建一个内部对象,并对其进行初始化,而等到调用的时候,才将此内部对象作为实参传递给执行对象:std::bind
表达式1在创建的时候就已经将vi的值进行了移动操作,编译器会在闭包类内部创建一个成员变量vi,然后将外部的vi通过移动构造来初始化闭包类内的vi,而不是在调用的时候才执行。同样,表达式2在创建绑定对象的时候就已经将vi的值移动到了内部一个成员变量,然后等到真正调用f2的时候,才将内部的这个变量传递给形参v,所以这里的形参v并不是针对vi的,是针对内部变量的。此外,默认情况下lambda表达式闭包里的所有变量都是const的(因为其std::vector<int> vi; auto f1 = [vi = std::move(vi)]() {std::cout << vi[0];}; // 1 auto f2 = std::bind([](const std::vector<int>& v){ std::cout << v[0];}, std::move(vi)); // 2 bind返回的是一个绑定对象,也是可调用对象
的声明默认是const的,所以成员变量在const成员函数内部都会隐式加上const属性(通过this指针加上的)),而绑定对象里移动构造得到的vi副本并不带有const属性,所以为了防止vi副本本修改,所以其对应的lambda表达式的形参被声明为了const引用,这样表达式2的行为就会与表达式1一致了。如果lambda表达式声明的时候带有operator()
,则闭包里的mutable
声明就不会加上const,因此lambda表达式内部的成员变量就未加上const修饰符了。此时如果想让表达式2与表达式1一致,则其形参也要去除const:operator()
所以说,mutable只是去除了lambda表达式生成的闭包中std::vector<int> vi; auto f1 = [vi = std::move(vi)]() mutable {std::cout << vi[0];}; // 1 auto f2 = std::bind([](std::vector<int>& v) mutable { std::cout << v[0];}, std::move(vi)); // 2
成员函数的const属性,让闭包中的成员函数在operator()
成员函数内部不再有const属性了,可以通过C++在线代码解析查看编译器展开后的实现;operator()
- C++14中,使用初始化捕获(即广义捕获)可以对象移动到闭包内(而不是复制或者通过引用传递,是移动操作)。在C++11中,没有初始化捕获,可以通过
将对象移动到绑定对象(由std::bind
生成的一种可调用对象)中来模拟初始化捕获;std::bind
- 当x是一个万能引用时(即
),此时T&& x
的类型要么是左值引用(当传入的实参是左值时),要么是一个右值引用(当传入的实参是一个右值时),不会是左值或者右值;decltype(x)
- 带有完美转发功能的lambda表达式的书写格式:
// 单形参格式 auto f = [](auto&& param) { return func(std::forward<decltype(param)>(param))); } // 任意多形参格式 auto f = [](auto&&... params) { return func(std::forward<decltype(params)>(params)...)); }
- lambda表达式可以完成
的功能,且可读性更好,且更容易生成效率更高的代码。并且不容易出错。所以尽量使用lambda表达式而不是std::bind
;std::bind
- 使用
传入形参的值是在调用std::bind
的时候求值的,而不是在执行其返回的可调用对象的时候求值的,所以当传入的是当前时间相关的变量时候,就有可能出错;std::bind
-
总是复制其实参到创建的绑定对象中,如果想传引用,则得使用std::bind
对实参进行包装。不过,后面调用的时候,将std::ref
中的那些副本参数传给指定函数是通过万能引用来传递的,也就是传递的是引用;std::bind
-
在C++14及更新的版本中毫无用武之地(使用lambda代替它),在C++11中仅在两个场合还算有使用的理由:移动捕获、多态函数对象。std::bind