求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 


业务架构设计
4月18-19日 在线直播



基于UML和EA进行系统分析设计
4月25-26日 北京+在线



AI 智能化软件测试方法与实践
5月23-24日 上海+在线
 
追随技术信仰

随时听讲座
每天看新闻
 
 
C++并发编程(中文版)
前言
第1章 你好,C++的并发世界!
1.1 何谓并发
1.2 为什么使用并发?
1.3 C++中的并发和多线程
1.4 开始入门
1.5 本章总结
第2章 线程管理
2.1 线程管理的基础
2.2 向线程函数传递参数
2.3 转移线程所有权
2.4 运行时决定线程数量
2.5 识别线程
2.6 本章总结
第3章 线程间共享数据
3.1 共享数据带来的问题
3.2 使用互斥量保护共享数据
3.3 保护共享数据的替代设施
3.4 本章总结
第4章 同步并发操作
4.1 等待一个事件或其他条件
4.2 使用期望等待一次性事件
4.3 限定等待时间
4.4 使用同步操作简化代码
4.5 本章总结
第5章 C++内存模型和原子类型操作
5.1 内存模型基础
5.2 C++中的原子操作和原子类型
5.3 同步操作和强制排序
5.4 本章总结
第6章 基于锁的并发数据结构设计
6.1 为并发设计的意义何在?
6.2 基于锁的并发数据结构
6.3 基于锁设计更加复杂的数据结构
6.4 本章总结
第7章 无锁并发数据结构设计
7.1 定义和意义
7.2 无锁数据结构的例子
7.3 对于设计无锁数据结构的指导建议
7.4 本章总结
第8章 并发代码设计
8.1 线程间划分工作的技术
8.2 影响并发代码性能的因素
8.3 为多线程性能设计数据结构
8.4 设计并发代码的注意事项
8.5 在实践中设计并发代码
8.6 本章总结
第9章 高级线程管理
9.1 线程池
9.2 中断线程
9.3 本章总结
第10章 多线程程序的测试和调试
10.1 与并发相关的错误类型
10.2 定位并发错误的技术
10.3 本章总结
附录A C++11语言特性简明参考(部分)
A .1 右值引用
A .2 删除函数
A .3 默认函数
A .4 常量表达式函数
A .5 Lambda函数
A .6 变参模板
A .7 自动推导变量类型
A .8 线程本地变量
A .9 本章总结
附录B 并发库的简单比较
附录C 消息传递框架与完整的ATM示例
 附录D C++线程库参考
D .1 chrono头文件
D .2 <condition_variable>头文件
D .3 <atomic>头文件
D .4 future头文件
D .5 <mutex>头文件
D .6 <ratio>头文件
D .7 <thread>头文件
 

 
目录
Lambda函数
作者:Anthony Williams  译者:陈晓伟
157 次浏览
12次  

A.5 Lambda函数

lambda函数在C++11中的加入很是令人兴奋,因为lambda函数能够大大简化代码复杂度(语法糖:利于理解具体的功能),避免实现调用对象。C++11的lambda函数语法允许在需要使用的时候进行定义。能为等待函数,例如std::condition_variable(如同4.1.1节中的例子)提供很好谓词函数,其语义可以用来快速的表示可访问的变量,而非使用类中函数来对成员变量进行捕获。

最简单的情况下,lambda表达式就一个自给自足的函数,不需要传入函数仅依赖管局变量和函数,甚至都可以不用返回一个值。这样的lambda表达式的一系列语义都需要封闭在括号中,还要以方括号作为前缀:

  1. []{ // lambda表达式以[]开始
  2. do_stuff();
  3. do_more_stuff();
  4. }(); // 表达式结束,可以直接调用

例子中,lambda表达式通过后面的括号调用,不过这种方式不常用。一方面,如果想要直接调用,可以在写完对应的语句后,就对函数进行调用。对于函数模板,传递一个参数进去时很常见的事情,甚至可以将可调用对象作为其参数传入;可调用对象通常也需要一些参数,或返回一个值,亦或两者都有。如果想给lambda函数传递参数,可以参考下面的lambda函数,其使用起来就像是一个普通函数。例如,下面代码是将vector中的元素使用std::cout进行打印:

  1. std::vector<int> data=make_data();
  2. std::for_each(data.begin(),data.end(),[](int i){std::cout<<i<<"\n";});

返回值也是很简单的,当lambda函数体包括一个return语句,返回值的类型就作为lambda表达式的返回类型。例如,使用一个简单的lambda函数来等待std::condition_variable(见4.1.1节)中的标志被设置。

清单A.4 lambda函数推导返回类型

  1. std::condition_variable cond;
  2. bool data_ready;
  3. std::mutex m;
  4. void wait_for_data()
  5. {
  6. std::unique_lock<std::mutex> lk(m);
  7. cond.wait(lk,[]{return data_ready;}); // 1
  8. }

 

lambda的返回值传递给cond.wait()①,函数就能推断出data_ready的类型是bool。当条件变量从等待中苏醒后,上锁阶段会调用lambda函数,并且当data_ready为true时,仅返回到wait()中。

当lambda函数体中有多个return语句,就需要显式的指定返回类型。只有一个返回语句的时候,也可以这样做,不过这样可能会让你的lambda函数体看起来更复杂。返回类型可以使用跟在参数列表后面的箭头(->)进行设置。如果lambda函数没有任何参数,还需要包含(空)的参数列表,这样做是为了能显式的对返回类型进行指定。对条件变量的预测可以写成下面这种方式:

  1. cond.wait(lk,[]()->bool{return data_ready;});

还可以对lambda函数进行扩展,比如:加上log信息的打印,或做更加复杂的操作:

  1. cond.wait(lk,[]()->bool{
  2. if(data_ready)
  3. {
  4. std::cout<<”Data ready”<<std::endl;
  5. return true;
  6. }
  7. else
  8. {
  9. std::cout<<”Data not ready, resuming wait”<<std::endl;
  10. return false;
  11. }
  12. });

 

虽然简单的lambda函数很强大,能简化代码,不过其真正的强大的地方在于对本地变量的捕获。

A.5.1 引用本地变量的Lambda函数

lambda函数使用空的[](lambda introducer)就不能引用当前范围内的本地变量;其只能使用全局变量,或将其他值以参数的形式进行传递。当想要访问一个本地变量,需要对其进行捕获。最简单的方式就是将范围内的所有本地变量都进行捕获,使用[=]就可以完成这样的功能。函数被创建的时候,就能对本地变量的副本进行访问了。

实践一下,看一下下面的例子:

  1. std::function<int(int)> make_offseter(int offset)
  2. {
  3. return [=](int j){return offset+j;};
  4. }

当调用make_offseter时,就会通过std::function<>函数包装返回一个新的lambda函数体。

这个带有返回的函数添加了对参数的偏移功能。例如:

  1. int main()
  2. {
  3. std::function<int(int)> offset_42=make_offseter(42);
  4. std::function<int(int)> offset_123=make_offseter(123);
  5. std::cout<<offset_42(12)<<”,“<<offset_123(12)<<std::endl;
  6. std::cout<<offset_42(12)<<”,“<<offset_123(12)<<std::endl;
  7. }

 

屏幕上将打印出54,135两次,因为第一次从make_offseter中返回,都是对参数加42的;第二次调用后,make_offseter会对参数加上123。所以,会打印两次相同的值。

这种本地变量捕获的方式相当安全,所有的东西都进行了拷贝,所以可以通过lambda函数对表达式的值进行返回,并且可在原始函数之外的地方对其进行调用。这也不是唯一的选择,也可以通过选择通过引用的方式捕获本地变量。在本地变量被销毁的时候,lambda函数会出现未定义的行为。

下面的例子,就介绍一下怎么使用[&]对所有本地变量进行引用:

  1. int main()
  2. {
  3. int offset=42; // 1
  4. std::function<int(int)> offset_a=[&](int j){return offset+j;}; // 2
  5. offset=123; // 3
  6. std::function<int(int)> offset_b=[&](int j){return offset+j;}; // 4
  7. std::cout<<offset_a(12)<<”,”<<offset_b(12)<<std::endl; // 5
  8. offset=99; // 6
  9. std::cout<<offset_a(12)<<”,”<<offset_b(12)<<std::endl; // 7
  10. }

 

之前的例子中,使用[=]来对要偏移的变量进行拷贝,offset_a函数就是个使用[&]捕获offset的引用的例子②。所以,offset初始化成42也没什么关系①;offset_a(12)的例子通常会依赖与当前offset的值。在③上,offset的值会变为123,offset_b④函数将会使用到这个值,同样第二个函数也是使用引用的方式。

现在,第一行打印信息⑤,offset为123,所以输出为135,135。不过,第二行打印信息⑦就有所不同,offset变成99⑥,所以输出为111,111。offset_a和offset_b都对当前值进行了加12的操作。

尘归尘,土归土,C++还是C++;这些选项不会让你感觉到特别困惑,你可以选择以引用或拷贝的方式对变量进行捕获,并且你还可以通过调整中括号中的表达式,来对特定的变量进行显式捕获。如果想要拷贝所有变量,而非一两个,可以使用[=],通过参考中括号中的符号,对变量进行捕获。下面的例子将会打印出1239,因为i是拷贝进lambda函数中的,而j和k是通过引用的方式进行捕获的:

  1. int main()
  2. {
  3. int i=1234,j=5678,k=9;
  4. std::function<int()> f=[=,&j,&k]{return i+j+k;};
  5. i=1;
  6. j=2;
  7. k=3;
  8. std::cout<<f()<<std::endl;
  9. }

 

或者,也可以通过默认引用方式对一些变量做引用,而对一些特别的变量进行拷贝。这种情况下,就要使用[&]与拷贝符号相结合的方式对列表中的变量进行拷贝捕获。下面的例子将打印出5688,因为i通过引用捕获,但j和k

通过拷贝捕获:

  1. int main()
  2. {
  3. int i=1234,j=5678,k=9;
  4. std::function<int()> f=[&,j,k]{return i+j+k;};
  5. i=1;
  6. j=2;
  7. k=3;
  8. std::cout<<f()<<std::endl;
  9. }

 

如果你只想捕获某些变量,那么你可以忽略=或&,仅使用变量名进行捕获就行;加上&前缀,是将对应变量以引用的方式进行捕获,而非拷贝的方式。下面的例子将打印出5682,因为i和k是通过引用的范式获取的,而j是通过拷贝的方式:

  1. int main()
  2. {
  3. int i=1234,j=5678,k=9;
  4. std::function<int()> f=[&i,j,&k]{return i+j+k;};
  5. i=1;
  6. j=2;
  7. k=3;
  8. std::cout<<f()<<std::endl;
  9. }

 

最后一种方式,是为了确保预期的变量能被捕获,在捕获列表中引用任何不存在的变量都会引起编译错误。当选择这种方式,就要小心类成员的访问方式,确定类中是否包含一个lambda函数的成员变量。类成员变量不能直接捕获,如果想通过lambda方式访问类中的成员,需要在捕获列表中添加this指针,以便捕获。下面的例子中,lambda捕获this后,就能访问到some_data类中的成员:

  1. struct X
  2. {
  3. int some_data;
  4. void foo(std::vector<int>& vec)
  5. {
  6. std::for_each(vec.begin(),vec.end(),
  7. [this](int& i){i+=some_data;});
  8. }
  9. };

 

并发的上下文中,lambda是很有用的,其可以作为谓词放在std::condition_variable::wait()(见4.1.1节)和std::packaged_task<>(见4.2.1节)中;或是用在线程池中,对小任务进行打包。也可以线程函数的方式std::thread的构造函数(见2.1.1),以及作为一个并行算法实现,在parallel_for_each()(见8.5.1节)中使用。


您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码: 验证码,看不清楚?请点击刷新验证码 必填



157 次浏览
12次