lambda
表达式的形式lambda
表达式的形式(可以省略参数列表和返回类型,但必须包含捕获列表和函数体):
[capture list](parameter list) -> return type {function body}
更详细点:
[捕获参数列表](函数参数列表) mutable throw(类型)->返回值类型 {函数语句};
空捕获列表表明lambda
不使用它所在函数中的任何局部变量。
lambda
不能有默认参数,因此一个lambda
调用的实参数目永远与形参数目相等。
lambda
的调用方式与普通函数的调用方式相同,都是使用调用运算符。
#ifndef TEST_LAMBDA_H_
#define TEST_LAMBDA_H_
#include
#include
#include
#include
#include
namespace test_lambda
{
auto main() -> int
{
std::cout << "Testing lambda......" << std::endl;
auto f = [](){int a = 9; return 8;};
std::cout << f() << std::endl; // 8
std::cout << typeid(f()).name() << std::endl; // int
std::vector<std::string> strs{"1234567890", "1", "123456", "123", "12345678"};
std::sort(strs.begin(), strs.end(), [](const std::string& a, const std::string& b){return a.size() < b.size();});
for(int i = 0; i < strs.size(); ++i){
std::cout << strs[i] << std::endl;
}
std::cout << "------------------------------" << std::endl;
return 0;
}
}
#endif
输出:
Testing lambda......
8
int
1
123
123456
12345678
1234567890
------------------------------
lambda
表达式的本质#include
#include
namespace test_lambda
{
auto main() -> int
{
std::cout << "Testing lambda......" << std::endl;
/* 1. lambda expression只是一个表达式expression。
2. 一个闭包closure是一个被lambda创建的运行时对象object。
3. 一个闭包类closure class源自创建闭包实例的类,每个lambda都会让编译器产生一个独特的闭包类(即闭包类创建了lambda对象)。
闭包是可以被拷贝的,因此一个闭包类型closure type可能生成很多个闭包closures。
*/
{
int i = 9;
auto c0 = [i]()mutable{
std::cout << "i=" << ++i;
std::cout << ", lambda expression defined in: " << __func__ << ", " << __LINE__ << std::endl; // 在__func__的位置会输出"operator ()",证实了c0内心住着一个functor
};
/* 可以拷贝闭包,但拷贝完成之后就"各自为政"了,谁也不影响谁,
比如进行值捕获的i,在c0、c1、c2、c3中是各自有一个副本,彼此互不影响;但对每个闭包调用多次会修改闭包内变量i的值,
总的来说,对闭包进行拷贝时,如果是值捕获则被捕获的变量在各个拷贝的闭包独自运行互不影响;如果是引用捕获则各个拷贝共同修改被捕获变量 */
auto c1 = c0;
auto c2 = c1;
auto c3 = c2;
c0(); // 10
c2(); // 10,虽然执行过一次c0(),且c2是c1的拷贝,c1是c0的拷贝,但两次拷贝时都没有对c0进行调用过,因此调用完c2,c2内部的i都是拷贝时的10
c3(); // 10
c3(); // 11
c2(); // 11,第二次调用c2会修改c2内部的i,因此i此时变成11
c1(); // 10
c0(); // 11
auto c4 = c3;
c4(); // 12,c4是c3的拷贝,拷贝时c3内i已经是11了,因此调用一次c4后c4内部的i会变成12
std::cout << "i=" << i << std::endl; // 9
std::cout << typeid(c0).name() << std::endl; // 输出class ,证实lambda其实是一个类对象
}
/* 注意:有人可能觉得值捕获多次调用lambda表达式并不会不断修改捕获的变量的副本,
因为似乎看起来每调用一次都是在局部区域修改变量的值,调用结束之后就出了作用域了,
比如两次调用c0()输出的结果似乎都应该是10,而不应该每次调用都会加1。
但实际上应该将每个lambda表达式看成一个闭包closure、一个对象,
每次调用c0()其实都是对同一个对象的操作,因此捕获变量的值每次调用都会产生变化。*/
std::cout << "------------------------------" << std::endl;
{
/* 按引用捕获会导致闭包包含对局部变量的引用,一旦由lambda创建的闭包越过了该局部变量的生命期,那么闭包内部的引用就会悬空 */
int i = 9;
auto c0 = [&i]()mutable{
std::cout << "i=" << ++i;
std::cout << ", lambda expression defined in: " << __func__ << ", " << __LINE__ << std::endl;
};
/* 引用捕获则各个拷贝共同修改被捕获变量 */
decltype(c0) c1 = c0;
decltype(c1) c2 = c1;
decltype(c2) c3 = c2;
c0(); // 10
c2(); // 11
c3(); // 12
c3(); // 13
c2(); // 14
c1(); // 15
std::cout << "i=" << i << std::endl; // 15
std::cout << typeid(c0).name() << std::endl;
}
return 0;
}
}
输出:
Testing lambda......
i=10, lambda expression defined in: operator (), 226
i=10, lambda expression defined in: operator (), 226
i=10, lambda expression defined in: operator (), 226
i=11, lambda expression defined in: operator (), 226
i=11, lambda expression defined in: operator (), 226
i=10, lambda expression defined in: operator (), 226
i=11, lambda expression defined in: operator (), 226
i=12, lambda expression defined in: operator (), 226
i=9
class <lambda_afcedac3f7c7146c1c21461d632c0c31>
------------------------------
i=10, lambda expression defined in: operator (), 255
i=11, lambda expression defined in: operator (), 255
i=12, lambda expression defined in: operator (), 255
i=13, lambda expression defined in: operator (), 255
i=14, lambda expression defined in: operator (), 255
i=15, lambda expression defined in: operator (), 255
i=15
class <lambda_c3fd70843e0a34e585cc39112e403eb6>
lambda
表达式捕获的都是局部变量一个lambda
只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中使用该变量。
捕获列表只用于局部非static
变量。lambda
可以直接使用局部static
变量和它所在函数之外声明的名字。
#pragma once
#include
#include
#include
#include
namespace test_lambda
{
// str是函数局部非static变量,出现在捕获列表中,lambda表达式可以使用
// std::cout是函数外定义的变量,可以在lambda中使用
void printBigger(const std::string &str, const std::vector<std::string>& words){
std::for_each(words.begin(), words.end(), [str](const std::string& it){
if(it.size() > str.size()) std::cout << it << std::endl;});
}
auto main()-> void{
std::cout << "test_lambda......." << std::endl;
std::vector<std::string> strs{"1234567890", "1", "123456", "123", "12345678"};
printBigger("abcd", strs);
std::cout << "test_lambda pass\n----------------------------" << std::endl;
}
}
输出:
test_lambda.......
1234567890
123456
12345678
test_lambda pass
----------------------------
示例:
#pragma once
#include
#include
#include
#include
namespace test_lambda
{
void printBigger(const std::string &str, const std::vector<std::string>& words){
int i = 0;
std::for_each(words.begin(), words.end(), [str](const std::string& it){
if(it.size() > str.size()) {
std::cout << it << std::endl;
std::cout << i++ << std::endl; // error: 'i' is not captured
}});
}
auto main()-> void{
std::cout << "test_lambda......." << std::endl;
std::vector<std::string> strs{"1234567890", "1", "123456", "123", "12345678"};
printBigger("abcd", strs);
std::cout << "test_lambda pass\n----------------------------" << std::endl;
}
}
#pragma once
#include
#include
#include
#include
namespace test_lambda
{
void printBigger(const std::string &str, const std::vector<std::string>& words){
static int i = 0; // 静态局部变量
std::for_each(words.begin(), words.end(), [str](const std::string& it){ // 并未捕获i
if(it.size() > str.size()) {
std::cout << it << std::endl;
std::cout << i++ << std::endl;
}});
}
auto main()-> void{
std::cout << "test_lambda......." << std::endl;
std::vector<std::string> strs{"1234567890", "1", "123456", "123", "12345678"};
printBigger("abcd", strs);
std::cout << "test_lambda pass\n----------------------------" << std::endl;
}
}
输出:
test_lambda.......
1234567890
0
123456
1
12345678
2
test_lambda pass
----------------------------
示例:
#pragma once
#include
#include
#include
#include
namespace test_lambda
{
void printBigger(const std::string &str, const std::vector<std::string>& words){
static int i = 0; // 对于局部静态变量,如果主动捕获会有警告
std::for_each(words.begin(), words.end(), [str,i](const std::string& it){ // warning: capture of variable 'i' with non-automatic storage duration
if(it.size() > str.size()) {
std::cout << it << std::endl;
std::cout << i++ << std::endl;
}});
}
auto main()-> void{
std::cout << "test_lambda......." << std::endl;
std::vector<std::string> strs{"1234567890", "1", "123456", "123", "12345678"};
printBigger("abcd", strs);
std::cout << "test_lambda pass\n----------------------------" << std::endl;
}
}
输出:
test_lambda.......
1234567890
0
123456
1
12345678
2
test_lambda pass
----------------------------
lambda
表达式定义了类型和对应的变量当向一个函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象: 传递的参数就是此编译器生成的类类型的未命名对象; 类似的,当使用auto定义一个用lambda初始化的变量时,定义了一个从lambda生成的类型的对象。
#include
#include
namespace test_lambda
{
auto main() -> int
{
std::cout << "Testing lambda......" << std::endl;
auto f = [](){int a = 9; return 8;};
std::cout << typeid(f).name() << std::endl; // [Visual Studio 15 2017] class
std::cout << typeid(f()).name() << std::endl; // int
std::cout << "------------------------------" << std::endl;
return 0;
}
}
输出:
Testing lambda......
class <lambda_d95bb8f939d4d5414300050c2be20dce>
int
------------------------------
类似函数传递,变量的捕获方式可以是值捕获或引用捕获。
与传值参数类似,采用值捕获的前提是变量可以拷贝。与参数不同,被捕获的变量的值是在lambda
创建时拷贝,而不是调用时拷贝。
示例:
#include
namespace test_lambda
{
auto main() -> int
{
std::cout << "Testing lambda......" << std::endl;
int i = 999;
auto f = [i](){return i;}; // 被捕获的变量的值是在`lambda`创建时拷贝,而不是调用时拷贝。
i = 888;
std::cout << f() << std::endl; // 999而不是888
std::cout << "------------------------------" << std::endl;
return 0;
}
}
输出:
Testing lambda......
999
------------------------------
值捕获函数内部局部变量时不会修改局部变量的值,但是值捕获全局变量的时候会修改全局变量的值,原因未知。
#pragma once
#include
int gi = 2;
namespace test_lambda
{
auto main()-> void{
std::cout << "test_lambda......." << std::endl;
int i = 2;
auto lambd = [=]()mutable{std::cout << "i = " << ++i << ", gi = " << ++gi << std::endl;};
lambd();
// 此时i仍然是2,但是gi已经变成了3
std::cout << "i = " << i << ", gi = " << gi << std::endl;
std::cout << "test_lambda pass\n----------------------------" << std::endl;
}
}
输出:
__cplusplus: 201703
test_lambda.......
i = 3, gi = 3
i = 2, gi = 3
test_lambda pass
----------------------------
#include
#include
#include
namespace test_lambda
{
using FuncVec = std::vector<std::function<int(void)>>;
class Wiget
{
public:
Wiget(int num):num_(num){std::cout << "Wiget ctor called." << std::endl;}
Wiget(const Wiget& wg):num_(wg.num_){std::cout << "Wiget copy-ctor called." << std::endl;}
~Wiget(){num_ = -1; std::cout << "~Wiget dtor called." << std::endl;}
std::function<int(void)> getfunc()const{
int copy_num = this->num_; // 使用num_的拷贝,防止Wiget对象析构后继续使用成员带来的未定义行为
auto lambda = [copy_num]() -> int{
return copy_num;
};
return lambda;
}
void print()const{
std::cout << "num_ = " << num_ << std::endl;
}
private:
int num_;
};
auto main() -> int
{
std::cout << "Testing lambda......" << std::endl;
#if 0
FuncVec fv;
{
Wiget wg(5);
fv.emplace_back(wg.getfunc());
std::cout << "fv[0]() = " << fv[0]() << std::endl;
} // 此时wg已经析构,但lambda中捕获的是局部变量copy_num,因此不会产生未定义行为
std::cout << "fv[0]() = " << fv[0]() << std::endl;
#endif
Wiget wg1(8);
auto lambda = [wg1](){wg1.print();};
lambda();
return 0;
}
}
输出:
Testing lambda......
Wiget ctor called.
Wiget copy-ctor called. // 此处说明值捕获会对对象进行拷贝
num_ = 8
~Wiget dtor called.
~Wiget dtor called.
以引用方式捕获变量时,在lambda
函数体内使用的是引用所绑定的对象。
当以引用方式捕获一个变量时,必须保证在lambda
执行时变量是存在的。例如,1.绑定前变量已经定义;2.绑定后在函数体结束之后不能再继续使用;3.从函数返回lambda
时此lambda
不能包含引用捕获。
#include
namespace test_lambda
{
auto main() -> int
{
std::cout << "Testing lambda......" << std::endl;
int i = 999;
auto f = [&i](){return i;}; // 引用捕获
i = 888;
std::cout << f() << std::endl; // 888
std::cout << "------------------------------" << std::endl;
return 0;
}
}
输出:
Testing lambda......
888
------------------------------
#include
namespace test_lambda
{
auto main() -> int
{
std::cout << "Testing lambda......" << std::endl;
int i = 9;
float j = 3.14f;
// auto foo = [=]{i += 1; j = j * j;}; // error C3491: “i”: 无法在非可变 lambda 中修改通过复制捕获
// error C3491: “j”: 无法在非可变 lambda 中修改通过复制捕获
auto bar = [&]{i += 1; j = j * j;};
bar();
std::cout << i << ", \t" << j << std::endl;
std::cout << "------------------------------" << std::endl;
return 0;
}
}
输出:
Testing lambda......
10, 9.8596
------------------------------
#include
namespace test_lambda
{
auto main() -> int
{
std::cout << "Testing lambda......" << std::endl;
int i = 9;
float j = 3.14f;
auto foo = [=, &j]()->int{
j = j * j;
return i + 1;
};
std::cout << foo() << std::endl;
std::cout << i << ", \t" << j << std::endl;
std::cout << "------------------------------" << std::endl;
return 0;
}
}
输出:
Testing lambda......
10
9, 9.8596
------------------------------
#include
namespace test_lambda
{
auto main() -> int
{
std::cout << "Testing lambda......" << std::endl;
int i = 9;
float j = 3.14f;
// =已经指定是值捕获了,j就不能再是值捕获,即第二位必须是引用捕获,前后必须不同
auto foo = [=, j]()->int{ // error C3489: 当默认捕获模式为按复制捕获时,需要“&j”(=)
j = j * j;
return i + 1;
};
// &已经指定是引用捕获了,j就不能再是引用捕获,即第二位必须是值捕获,前后必须不同
auto bar = [&, &j]{}; // error C3488: 当默认捕获模式为按引用捕获时,无法显式捕获“&j”(&)
std::cout << "------------------------------" << std::endl;
return 0;
}
}
mutable
使用值捕获时lambda
不会改变变量的值。当想在lambda
函数体内部修改该变量的值但是又不想改变该变量在lambda
外部的值的时候可以使用mutable
。
#include
namespace test_lambda
{
auto main() -> int
{
std::cout << "Testing lambda......" << std::endl;
int i = 9;
auto foo = [i]()mutable->int {return ++i;};
std::cout << i << std::endl; // 9
std::cout << foo() << std::endl; // 10
std::cout << i << std::endl; // 9
std::cout << "------------------------------" << std::endl;
return 0;
}
}
输出:
Testing lambda......
9
10
9
------------------------------
mutable
吧?#include
namespace test_lambda
{
auto main() -> int
{
std::cout << "Testing lambda......" << std::endl;
int i = 9;
auto foo = [&i]()->int {return ++i;};
// auto foo = [&i]()mutable->int {return ++i;}; // 结果和上句一样
std::cout << i << std::endl; // 9
std::cout << foo() << std::endl; // 10
std::cout << i << std::endl; // 10
std::cout << "------------------------------" << std::endl;
return 0;
}
}
输出:
9
10
10
------------------------------
引用捕获时能不能改变捕获变量的值取决于捕获的对象是const
还是非const
。
引用捕获非const
示例:
#include
namespace test_lambda
{
auto main() -> int
{
std::cout << "Testing lambda......" << std::endl;
int i = 9;
auto foo = [&i]()->int {return ++i;};
i = 0;
std::cout << foo() << std::endl; // 1
std::cout << i << std::endl; // 1
std::cout << "------------------------------" << std::endl;
return 0;
}
}
输出:
Testing lambda......
1
1
------------------------------
引用捕获const
示例:
#include
namespace test_lambda
{
auto main() -> int
{
std::cout << "Testing lambda......" << std::endl;
const int i = 9;
auto foo = [&i]()->int {return ++i;}; // error C3491: “i”: 无法在非可变 lambda 中修改通过复制捕获
std::cout << "------------------------------" << std::endl;
return 0;
}
}
示例1:
#pragma once
#include
#include
namespace test_lambda
{
auto test0()-> void
{
int id = 0;
auto lambda0 = [id]()mutable{ // 此处的id跟上句不是同一个,可以看作副本
std::cout << "id: " << id << std::endl;
++id;
};
id = 42; // 此处修改id的值并不会修改上面lambda0中id的值,因为二者不是同一个变量
lambda0(); // 0,多次调用修改的是同一个变量id
lambda0(); // 1,多次调用修改的是同一个变量id
lambda0(); // 2,多次调用修改的是同一个变量id
std::cout << "id: " << id << std::endl; // 42,lambda0中是值传递,修改lambda0内id的副本不会改变外部id的值
}
auto test1()-> void
{
int id = 0;
auto lambda1 = [&id](){ // 传引用,lambda1内外部会联动,因此修改lambda1内部的id即是修改外部的id,二者是同一个变量id
std::cout << "id: " << id << std::endl;
++id;
};
id = 42;
lambda1(); // 42
lambda1(); // 43
lambda1(); // 44
std::cout << "id: " << id << std::endl; // 45
}
auto main()-> void
{
std::cout << "test_lambda......." << std::endl;
test0();
std::cout << std::string(20,'-') << std::endl;
test1();
std::cout << "test_lambda pass" << std::endl;
}
}
输出:
test_lambda.......
id: 0
id: 1
id: 2
id: 42
--------------------
id: 42
id: 43
id: 44
id: 45
test_lambda pass
示例2
#pragma once
#include
#include
namespace test_lambda
{
auto main()-> void
{
std::cout << "test_lambda......." << std::endl;
int a = 1, b = 1, c = 1;
auto m1 = [a, &b, &c]()mutable{
auto m2 = [a, b, &c]()mutable{
std::cout << a << b << c << std::endl;
a = 4; b = 4; c = 4;
};
a = 3; b = 3; c = 3;
m2();
};
a = 2; b = 2; c = 2;
m1();
std::cout << a << b << c << std::endl;
std::cout << "test_lambda pass" << std::endl;
}
}
输出:
test_lambda.......
123
234
test_lambda pass
执行m1()
,m1()
内部执行m2()
;
m2()
的c是pass by reference,因此其最终值取决于m2()
内的赋值,c=4
;
m2()
的b是pass by value,因此其最终值取决于m1()
内的赋值,b=3
;
a
在m1()
,m2()
内都是pass by value,因此其最终值取决于main()
内的赋值,a=2
。
lambda
的返回类型可以主动指定lambda
的返回类型,也可以让编译器自动推断。
注意:此处实测跟C++ Primer中文版P353页中的表述不一致,建议以实测为准。书中是说如果一个lambda体包含return之外的任何语句则编译器假定lambda返回void,(说实话,没看懂书上说的是啥意思)。
#include
#include
namespace test_lambda
{
auto main() -> int
{
std::cout << "Testing lambda......" << std::endl;
int i = 9;
auto foo = [i](){ // 未指定返回类型,自动推断为int
if(i >= 0){
return 1;
}
else{
return -1;
}
};
auto bar = [i]()->float{
if(i >= 0){
return 1;
}
else{
return -1;
}
};
std::cout << typeid(foo()).name() << std::endl;
std::cout << typeid(bar()).name() << std::endl;
std::cout << "------------------------------" << std::endl;
return 0;
}
}
输出:
Testing lambda......
int
float
------------------------------
在对象中捕获this
时小心dangle。
#include
#include
#include
namespace test_lambda
{
using FuncVec = std::vector<std::function<int(void)>>;
class Wiget
{
public:
Wiget(int num):num_(num){std::cout << "Wiget ctor called." << std::endl;}
Wiget(const Wiget& wg):num_(wg.num_){std::cout << "Wiget copy-ctor called." << std::endl;}
~Wiget(){num_ = -1; std::cout << "Wiget dtor called." << std::endl;}
std::function<int(void)> getfunc()const{
// 写法1,编译通过,但行为可能未定义
auto lambda = [this]() -> int{
//std::cout << this->num_ << std::endl;
return this->num_;
};
#if 0 // 写法2,编译通过,但行为可能未定义
auto lambda = [=]() -> int{
std::cout << this->num_ << std::endl;
return this->num_;
};
#endif
#if 0 // 写法3,编译错误
auto lambda = []() -> int{ // error C4573: “test_lambda::Wiget::num_”的用法要求编译器捕获“this”,但当前默认捕获模式不允许使用“this”
std::cout << num_ << std::endl;
return num_;
};
#endif
#if 0 // 写法4,编译错误
auto lambda = [num_]() -> int{ // error C3480: “test_lambda::Wiget::num_”: lambda 捕获变量必须来自封闭函数范围
std::cout << num_ << std::endl;
return num_;
};
#endif
return lambda;
}
private:
int num_;
};
auto main() -> int
{
std::cout << "Testing lambda......" << std::endl;
FuncVec fv;
{
Wiget wg(5);
fv.emplace_back(wg.getfunc());
std::cout << "fv[0]() = " << fv[0]() << std::endl;
} // 此时wg已经析构,this已经悬空dangle,lambda中继续使用之前捕获的num_已经是未定义行为
// 未定义行为意味着今天正确明天可能会出错,未定义行为不一定表示立即会崩溃或出错,跟编译器、编译选项、代码细节相关
// 很多时候开优化出错,也是因为未定义行为容易在开启优化的时候更容易暴露出来
std::cout << "fv[0]() = " << fv[0]() << std::endl;
return 0;
}
}
输出:
Testing lambda......
Wiget ctor called.
fv[0]() = 5
Wiget dtor called.
fv[0]() = -1
解决方法:
#include
#include
#include
namespace test_lambda
{
using FuncVec = std::vector<std::function<int(void)>>;
class Wiget
{
public:
Wiget(int num):num_(num){std::cout << "Wiget ctor called." << std::endl;}
~Wiget(){num_ = -1; std::cout << "~Wiget dtor called." << std::endl;}
std::function<int(void)> getfunc()const{
//std::cout << "num_=" << this->num_ << std::endl;
int copy_num = this->num_; // 使用num_的拷贝,防止Wiget对象析构后继续使用成员带来的未定义行为
auto lambda = [copy_num]() -> int{
//std::cout << "copy_num=" << copy_num << std::endl;
return copy_num;
};
return lambda;
}
private:
int num_;
};
auto main() -> int
{
std::cout << "Testing lambda......" << std::endl;
FuncVec fv;
{
Wiget wg(5);
fv.emplace_back(wg.getfunc());
std::cout << "fv[0]() = " << fv[0]() << std::endl;
} // 此时wg已经析构,但lambda中捕获的是局部变量copy_num,因此不会产生未定义行为
std::cout << "fv[0]() = " << fv[0]() << std::endl;
return 0;
}
}
输出:
Testing lambda......
Wiget ctor called.
fv[0]() = 5
~Wiget dtor called.
fv[0]() = 5