C++11的闭包(lambda、function、bind)
c++11开始支持闭包,闭包:与函数A调用函数B相比较,闭包中函数A调用函数B,可以不通过函数A给函数B传递函数参数,而使函数B可以访问函数A的上下文环境才可见(函数A可直接访问到)的变量;比如:
函数B(void) {
......
}
函数A {
int a = 10;
B(); //普通调用函数B
}
函数B无法访问a;但如果是按闭包的方式,则可以访问变量a:
函数A() {
int a = 10;
auto closure_B = [a]() {
......
}
}
所以闭包的优缺点很清晰,都是同一个:可以不通过传参获取调用者的上下文环境;
下面开始介绍c++11的闭包所涉及的内容:
1、lambda
1.1、lambda的定义
1.2、lambda的应用
1.3、lambda的使用注意事项
2、function
2.1、通过lambda的function
2.2、通过函数的function
1、lambda
1.1、lambda的定义:
匿名表达式的结构:auto lambda = [捕获列表] (参数列表) (修饰符) {匿名函数体};
关于捕获列表:捕获调用者上下文环境的需要访问的变量,可按值捕获或按引用捕获,其规则如下:
[]:什么也不捕获
[=]:捕获所有一切变量,都按值捕获
[&]:捕获所有一切变量,都按引用捕获
[=, &a]:捕获所有一切变量,除了a按引用捕获,其余都按值捕获
[&, =a]:捕获所有一切变量,除了a按值捕获,其余都按引用捕获
[a]:只按值捕获a
[&a]:只按引用捕获a
[a, &b]:按值捕获a,按引用捕获b
关于参数列表:
和函数调用的参数列表含义完全一样;
关于修饰符:
mutable:必须注意加入mutable可以修改的是:在lambda匿名函数体里边,按值捕获到的变量,实质上是调用者函数中变量的只读拷贝(read-only),加入了mutable后,匿名函数体内部可以修改这个拷贝的值,也就是说调用者函数中该变量的值依然不会被改变;
关于匿名函数体:
和函数没有区别;
1.2、lambda的应用:
a. 最简单的使用:
TEST (test1, lambda_1) {
//capture list is empty, all the args is value, so variable b will not change.
int a = , b = ;
auto lambda = [](int a, int b) {
b = a + a + a;
return a + b;
}; std::cout << lambda(a, b) << std::endl;
std::cout << b << std::endl;
}
这里捕获列表为空,完全相当于调用普通函数
b. 捕获列表非空:
TEST (test1, lambda_2) {
//capture list is not empty, x is value, y is reference, and arg z is reference
//so y will change to 1, z will change to 1
//x is value, so x is read-only in lambda, should not modify x in lambda
int x = , y = ;
auto lambda = [x, &y](int &z) {
z = x;
y = x;
//x = y;
return y;
}; int z = ;
std::cout << lambda(z) << std::endl;
std::cout << y << std::endl;
std::cout << z << std::endl;
}
捕获列表非空,变量x传值,变量y传引用,并且传一个参数z;
c. 捕获全部变量 & 使用mutable修饰符:
TEST (test1, lambda_3) {
//'=' in capture list means that all the variables will as value in lambda, so should not modify a, a is read-only in lambda
int a = ;
auto lambda_val = [=]() {
//a = 10;
return a + ;
};
std::cout << lambda_val() << std::endl;
std::cout << a << std::endl; //'&' '=' in capture list means that all the variables will as reference in lambda, so could modify a
auto lambda_ref = [&]() {
a = a + ;
return a + ;
};
std::cout << lambda_ref() << std::endl;
std::cout << a << std::endl; //though '=' in capture list means all the variables as value in lambda_val_mutable, but lambda_val_mutable is mutable, so also could modify a
auto lambda_val_mutable = [=]() mutable {
a = ;
return a + ;
};
std::cout << lambda_val_mutable() << std::endl;
std::cout << a << std::endl;
}
使用[=]按值、[&]按引用捕获一切变量;使用mutable修饰符,在匿名函数体内部修改按值传入的变量,注意在调用者函数不会被修改生效;
d. lambda的定义时初始化:
TEST (test1, lambda_4) {
//all capture variables will value in lambda, so a will not change, return a + 1 = 2
int a = ; a = ;
auto lambda_val = [=]() {
//a = b;
return a + ;
}; int b = ;
std::cout << lambda_val() << std::endl;
std::cout << a << std::endl; //a is change to 10, but for lambda_val, a is inited(read-only), will not change to 10, forever is 1
a = ;
std::cout << lambda_val() << std::endl;
std::cout << a << std::endl;
}
lambda表达式定义时,调用者上下文环境中的变量有哪些、值都是什么,是"一次性"初始化的,也就是说即便后面值被修改,但lambda是无法获知的,后面再创建的新的变量,lambda是无法访问的;
e. 部分变量传引用或者传值:
TEST (test1, lambda_5) {
//exclude b is reference in lambda, all the other variables in capture list will be value in lambda
int a = , b = , c = , d = , e = ;
auto lambda = [=, &b](int f) {
b = a + c + d + e;
return a + b + c + d + e;
}; std::cout << lambda() << std::endl;
std::cout << b << std::endl;
}
f. 类成员函数使用lambda:
TEST (test1, lambda_6) {
//in a class-function, lambda's capture list is this point, so could access and modify the class non-const variable
class cls {
int a;
int b;
int c;
const int d;
public:
cls():a(), b(), c(), d() {}
~cls(){}
void testlambda() {
auto lambda = [this]() {
a = ;
//d = 1;
return a + b + c + d;
}; std::cout << a << std::endl;
std::cout << lambda() << std::endl;
std::cout << a << std::endl;
}
}; cls c;
c.testlambda();
}
this指针进入捕获列表,匿名函数体内部即可调用类成员变量;
g. c++11风格的lambda的使用:
TEST (test1, lambda_7) {
std::vector<int> v = {,,,,,,,,,}; std::sort(begin(v), end(v), [](int i, int j) {
return i > j;
}); std::for_each(begin(v), end(v), [](int i) {
std::cout << i << " ";
});
std::cout << std::endl; int total = ;
for_each (begin(v), end(v), [&](int i) {
total += i;
});
std::cout << total << std::endl; int base = ;
for_each (begin(v), end(v), [=](int i) mutable {
total += (i * base);
std::cout << total << "," << i << "," << base << std::endl;
});
std::cout << total << std::endl; for_each (begin(v), end(v), [&](int i) mutable {
total += (i * base);
std::cout << total << "," << i << "," << base << std::endl;
});
std::cout << total << std::endl;
}
像python一样的使用;
1.3、lambda使用的注意事项:
lambda常常作为线程执行函数使用,这时尤其要注意调用者上下文环境的变量(及其指向的内存空间)的生命周期,是否能够和以lambda作为线程执行函数的线程的生命周期一样长,如下面的两个例子:
会导致出问题的:
TEST (test3, closure_wrong) {
int *a = new int(10);
std::thread th = std::thread([&a] () {
while (1) {
std::cout << "a: " << a << ", *a: " << *a << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
std::this_thread::sleep_for(std::chrono::seconds(3));
delete a;
a = nullptr;
std::this_thread::sleep_for(std::chrono::seconds(30));//closure will collapse...
}
动态变量a申请了4字节的动态空间,作为线程执行函数的匿名函数使用了变量a指向的动态空间;然后a在调用者函数内部释放了动态空间,但匿名函数依然访问该动态空间,此时导致发生程序崩溃;
不会导致出问题的:
TEST (test3, closure_right) {
int *a = new int(10);
std::thread th = std::thread([a] () {
while (1) {
std::cout << "a: " << a << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
th.detach();
std::this_thread::sleep_for(std::chrono::seconds(3));
delete a;
a = nullptr;
std::this_thread::sleep_for(std::chrono::seconds(30));//closure will not collapse
}
上面的程序不会出现问题,因为匿名函数内部没有访问变量a指向的动态空间。但依然是不推荐的方式。应确保匿名函数访问的外部捕获变量,不会在线程生命周期内失效;
2、function:
掌握了lambda,function就非常容易掌握;c++11定义一个函数std::function,可以绑定一个匿名函数表达式lambda,也可以绑定一个普通函数(包括类成员函数);
2.1、绑定lambda匿名函数:
TEST (test2, function_lambda) {
int a = 1, b = 2;
std::function<int (const int &)> func1 = std::function<int (const int &)>([a, &b] (const int &i) {
b = a + i;
return a + b;
});
int c = 10;
std::cout << func1(std::cref(c)) << std::endl;
std::cout << a << std::endl;
std::cout << b << std::endl;
std::function<int (const int &, int &)> func2 = std::function<int (const int &, int &)>([a, &b] (const int &i, int &j) {
int k = i + j;
b = a + k;
j = a + b;
return a + b + j;
});
int d = 10, e = 20;
std::cout << func2(d, e) << std::endl;
std::cout << b << std::endl;
std::cout << e << std::endl;
class cls {
int a;
int b;
int c;
public:
cls(int x, int y, int z):a(x), b(y), c(z){}
~cls(){}
double CalcAverage() {
auto calcer = std::function<double ()>([this] {
return (a + b + c)/3.0;
});
return calcer();
}
};
std::vector<int> v;
std::random_device rd;
for (auto idx: common::Range(0, 3)) {
int i = rd() % 10;
v.push_back(i);
std::cout << "input " << i << std::endl;
}
cls cl(v[0], v[1], v[2]);
double average = cl.CalcAverage();
std::cout << std::fixed << std::setprecision(6) << average << std::endl;
}
函数func1、func2就是绑定lambda的匿名函数;而cls的类成员函数CalcAverage,就是编写了一个匿名函数calcer再调用的闭包方式;
2.2、绑定普通函数(包括类成员函数):
int func1 (const int &i, const int j, int &k) {
k = i - j;
return i + j;
}
int func2 (int &i, double j, const std::string k) {
return i + (int)j;
}
TEST (test2, function_simple_and_bind) {
int i = 0, j = 1, k = 2;
auto f1 = std::function<int (const int &, const int, int &)>(func1);
std::cout << f1(std::cref(i), j, std::ref(k)) << std::endl;
std::cout << k << std::endl;
//if use bind and adjust the arg order, such as: arg_3 is actively arg_1, arg_1 is actively arg_2, arg_2 is actively arg_3,
//the std::function's arg define, must obey to the active datastruct,
//for example as follow, arg_1 is const int, arg_2 is int&, arg_3 is const int &, so f2's arg order is "const int + int & + const int &", and also when call f2.
auto f2 = std::function<int (const int, int &, const int &)>(std::bind(func1, std::placeholders::_3, std::placeholders::_1, std::placeholders::_2));
std::cout << f2(k, std::ref(j), std::cref(i)) << std::endl;//equal: func1(k, j, i)
std::cout << k << std::endl;
auto f3 = std::function<int (double)>(std::bind(func2, std::ref(j), std::placeholders::_1, "aaa"));
std::cout << f3(1.1) << std::endl;
class cls {
int a;
int b;
int c;
public:
cls(int x, int y, int z):a(x), b(y), c(z){}
~cls(){}
void Run() {
int i = 4;
auto func = std::function<void (int &)>([this, i] (int &base){
int times = 8;
while (times--) {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << std::fixed << std::setprecision(6) << (a + b + c + base)/i << std::endl;
base = a + b + c + base;
}
});
int base = 10;
func(std::ref(base));
}
};
cls cl(1, 2, 3);
cl.Run();
class cls2 {
int a;
int b;
int c;
public:
cls2(int x, int y, int z):a(x), b(y), c(z){}
~cls2(){}
void RealRun (int &base) {
int times = 8;
while (times--) {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << (a + b + c + base) << std::endl;
base = a + b + c + base;
}
}
void Run () {
int base = 10;
auto f = std::bind(&cls2::RealRun, this, base);
f();
}
};
cls2 cl2(1, 2, 3);
cl2.Run();
}
f1是直接绑定一个普通函数;
f2是通过std::bind绑定一个普通函数,并且修改了函数传参顺序;
f3也是通过std::bind绑定一个普通函数,进一步修改了函数传参个数;
这里描述下std::bind的传参个数和顺序,通过bind修改传参的个数或者顺序,实现对同一个普通函数实现(包括类成员函数),有任意的自定义传参的函数变体,如对于下面的函数A:
函数A (int, double, string) {......},可以实现如下变体:
f1 = std::bind(&A, 1, 1.1, std::placeholders::_1) //f1("abc") == A(1,1.1,"abc")
f2 = std::bind(&A, std::placeholders::_2, 1.1, std::placeholders::_1) //f2("abc",1) == A(1,1.1,"abc")
f3 = std::bind(&A, std::placeholders::_3,std::placeholders::_1,std::placeholders::_2) //f3(1.1,"abc",1) == A(1,1.1,"abc")
int b =1;
f4 = std::bind(&A, b,std::placeholders::_1,"abc") //f4(1.1) == A(1,1.1,"abc")
f1、f2、f3对应的函数定义都是函数A的函数定义,但是实现了不同形态的函数调用"变体",方便根据具体情况自定义;
cls类成员函数Run,通过lambda实现闭包;
cls2类成员函数Run,通过bind另一个成员函数RealRun实现闭包;
C++11的闭包(lambda、function、bind)的更多相关文章
- 学习C++11的一些思考和心得(1):lambda,function,bind和委托
1.lambda表达式 lanbda表达式简单地来讲就是一个匿名函数,就是没有名称的函数,如果以前有接触过python或者erlang的人都比较熟悉这个,这个可以很方便地和STL里面的算法配合 st ...
- 【转帖】漫话C++0x(四) —- function, bind和lambda
实在是觉得此文总是去翻感觉不太好.于是果断转过来了,想看原文的请戳:http://www.wuzesheng.com/?p=2032 本文是C++0x系列的第四篇,主要是内容是C++0x中新增的lam ...
- C++11 学习笔记 std::function和bind绑定器
C++11 学习笔记 std::function和bind绑定器 一.std::function C++中的可调用对象虽然具有比较统一操作形式(除了类成员指针之外,都是后面加括号进行调用),但定义方法 ...
- [置顶] C++ Pirate: Lambda vs Bind
Lambda 与 Bind的性能比较 转载请说明出处:http://blog.csdn.net/cywosp/article/details/9379403 先让我们看看下面函数: template ...
- js中闭包来实现bind函数的一段代码的分析
今天研究了一下bind函数,发现apply和call还可以有这样的妙用,顺便巩固复习了闭包. var first_object = { num: 42 }; var second_object = { ...
- C++ 类的成员函数指针 ( function/bind )
这个概念主要用在C++中去实现"委托"的特性. 但现在C++11 中有了 更好用的function/bind 功能.但对于类的成员函数指针的概念我们还是应该掌握的. 类函数指针 就 ...
- [C/C++11]_[初级]_[std::bind介绍和使用]
场景 1.C++11 引入了std::function 对象, 这个对象可以通过std::bind封装所有的函数, 并通过代理调用这个std::function的方式调用这个函数. 比如通过统一的方式 ...
- C++11中提供了std::bind
再来看看std::bind C++11中提供了std::bind.bind()函数的意义就像它的函数名一样,是用来绑定函数调用的某些参数的. bind的思想实际上是一种延迟计算的思想,将可调用对象保存 ...
- 【转】C++11新特性——lambda表达式
C++11的一大亮点就是引入了Lambda表达式.利用Lambda表达式,可以方便的定义和创建匿名函数.对于C++这门语言来说来说,“Lambda表达式”或“匿名函数”这些概念听起来好像很深奥,但很多 ...
随机推荐
- secureCRT 如何上传下载文件
首先连接相应服务器,然后在文件选项当中,打开SFTP功能,这个时候会生成一个新的标签栏. 下载: cd 到要下载文件的路径下 lcd 要存放文件的本地路径 get {filename} 例: cd ...
- 更新view是可以update到表的
视图不是表,视图里面的数据是通过sql语句去表中查询得到的.当表中的数据发送更改之后,视图里的数据也会发生相应的更改.所以我么一般有两种方式更新视图里面的数据:一是更新表中的数据,从而间接地更新视图中 ...
- codeforces round 433 D. Jury Meeting
题目大意: 输入n,m,k,分别代表城市的数量,城市编号1~n,航班的数量以及会议必须所有人员到会一起商议的天数,然后及时输入m行航班的信息,每一行输入d,f,t,c分别表示航班到站和始发的那一天(始 ...
- spring data jpa 配置文件
<?xml version="1.0" encoding="UTF-8"?><persistence xmlns="http://j ...
- redis实现点击量/浏览量
java+redis实现高性能新闻点击量更新 1.redis简单介绍.它用来做高性能数据存取 是极好的. 2.实例:新闻点击量. 1)每次刷新,我们并不一定要往数据库里面立即更新数据 2)可以在red ...
- LOJ6485 LJJ 学二项式定理 解题报告
LJJ 学二项式定理 题意 \(T\)组数据,每组给定\(n,s,a_0,a_1,a_2,a_3\),求 \[ \sum_{i=0}^n \binom{n}{i}s^ia_{i\bmod 4} \] ...
- NX二次开发-Block UI C++界面(表达式)控件的获取(持续补充)
Expression(表达式)控件的获取 NX9+VS2012 #include <uf.h> #include <uf_modl.h> UF_initialize(); // ...
- Ext 面板(Panel)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- atlcomcli.h(1756): error C2338: CVarTypeInfo< char > cannot be compiled with /J or _CHAR_UNSIGNED fl
我拿到一个VS的工程,用VS2010 编译 时提示: atlcomcli.h(1756): error C2338: CVarTypeInfo< char > cannot be comp ...
- netty源码分析 - Recycler 对象池的设计
目录 一.为什么需要对象池 二.使用姿势 2.1 同线程创建回收对象 2.2 异线程创建回收对象 三.数据结构 3.1 物理数据结构图 3.2 逻辑数据结构图(重要) 四.源码分析 4.2.同线程获取 ...