接下来我们看第二种
带回来结果的策略
这就是期许
在C++11标准库里边
替我们提供了一个新的类模板
叫std::future
它的目的就是为了获取
异步操作的结果
延迟引发线程的异步操作异常
使用方法 格式也是固定的
首先你要定义一个期许类模板的
一个期许对象
然后使用std::async()这个函数的返回值
对它进行初始化
然后你调用期许对象的成员函数get()
获取那个线程的返回值
听上去三步操作就能搞定
OK 我们看代码
我们实现一个函数叫CalculateFactorial()
去计算阶乘的
传的是一个short int
因为阶乘的计算增长得很快
所以不需要太大的整数
如果n是大于20的
我们就抛出异常
范围错误 就区间错误
抛出异常
20的阶乘我们能算
21的阶乘我们就算不了了
越界了 整数就越界了
数字就太大了 OK
函数的返回值
当然是一个unsigned long int
OK 函数本身很简单
我们看主函数 short int n
初始化成20
我们定义一个期许对象f
std::future〈unsigned long int〉
这是这个期许类的型
定义期许对象f
把它初始化成
std::async( CalculateFactorial, n)
什么意思
我们异步执行这个线程函数CalculateFactorial()
传线程函数参数n 去执行这个线程
它就会调度运行
这是后台计算 做完了
我们就通过期许对象f
得到那个线程的结果
try unsigned long intr
初始化为f.get()
这就把这个CalculateFactorial()
这个函数的结果就拿到了
你就输出就OK了 简单吧
这是很典型一个期许对象
你就把它搞定了
期许对象有什么好处呢
就是这个线程函数如果引发了异常
那么这个异常不会在那个线程中处理
线程内部可以处理
但处理完以后正常情况下你应该引发
待会我们再解释
如果那个运行的线程引发了异常
那么这个异常将会被延迟到std::future::get()
或者std::future::wait()
这两个成员函数被调用的时候
才会被引发
也就是我们的这个线程（主线程）
去处理劳工线程（它那个后台线程）
引发的那个异常
你可以按照这样一个方式去实现
try unsignedlong intr
初始化为f.get()
然后catch
按照这个模式去处理
接下来一个就是承诺
你可以使用这样的一个类模板
来构造一个承诺对象
它的使用方法
跟刚才那个期许对象相比
使用起来要稍微复杂一点
首先你要创建承诺对象
构造它的一个对象
然后获取
那个承诺对象的相关的期许对象
在此之后你要创建自己的线程对象
并传递这个承诺对象进去
然后线程函数内部
会通过承诺模板类的成员函数
set_value()、set_value_at_thread_exit()、
set_exception()、set_exception_at_thread_exit()
这样的函数
设置线程的返回值或者异常
然后我们就可以通过期许对象
等待并获取异步操作的结果
你看它实际上要和期许对象
一块运作的
为什么它叫承诺呢
那叫期许呢 这就很典型
你让一个线程去做一个异步的操作
它未来呢会带给你一个结果
主线程期望得到这个结果
就是它有期许
它对未来你给我算出来那个结果
它实际上是有期许的
这就是站在主线程角度
我们看待这个问题
它对劳工线程有一个期许
“你会给我一个结果的”
对吧 这就是这个future
未来会得到它
那么这边呢
当你构造这个线程对象的时候
那主线程要希望从你这里
得到一个数据呀 对吧 OK
当我构造这个模式的时候
对劳工线程来讲 我就承诺你
我会给你一个结果
就是这个意思
两者实现的策略 你看
这实际上是不一样的
我们看怎么使用它
我们使用承诺来设置线程的返回值
CalculateFactorial()这个函数还跟刚才一样
我们要为它实现一个打包类
要封装 DoCalculateFactorial()
传递一个承诺对象的右值引用
传递一个short int
那个线程函数的参数
传递承诺对象
因为这个标准线程函数
是没有承诺对象的
所以我们要用承诺机制的话
必须传递一个承诺对象进去
OK 我们的主程序中
将会定义这个承诺对象
构造一个
然后得到它对应的期许对象
每个承诺都会有一个对应的期许
所以我们构造出这个承诺对象之后
就直接在构造出来的承诺对象上
调用它的get_future()这个成员函数
得到它对应的期许对象就OK了
这是C++11标准库做好的
你构造这个对象
调用它的期许函数
就能够得到它的期许对象
然后我们启动线程
创建一个线程对象
把这个CalculateFactorial()打包函数
DoCalculateFactorial传进行
传承诺对象
因为是右值引用
所以我们传递右值 std::move(p) 传递它
第三个参数传n值
线程一构造 我们detach()
分离它 不管它了
让它在后台跑就行了
然后我们try f.get()
从期许对象中获取这个线程的返回值
如果我拿到了这个线程的返回值
我就输出
否则那个线程就引发了异常了
所以这个时候我们就catch
抓住这个异常
我们处理这个异常 就OK了
所以你看到
对于期许对象来讲
我们实际上是期许从
后台线程中得到结果的
主要是在我们的主程序里要做的事情
而我们的承诺对象呢
那是线程在做的时候
要承诺给你一个结果的
如果你那个线程本身
并没有带有承诺对象的参数
那么你就必须为你那个线程函数
做一个封包 打包
搞一个打包函数
在这里面传一个承诺对象进去
然后我们构造这个承诺对象
然后你就可以在构造线程的时候
传递这个承诺对象
给这个线程函数的打包函数
线程函数的打包函数就会说
“我承诺我会给你一个结果的，
你未来就可以使用我的期许对象
获得那个结果”
就这个意思 实现上非常巧妙
那么我们的DoCalculateFactorial()怎么实现呢
代码其实也很简单
try pormise.set_value() 设定值
我承诺给你一个结果啊
怎么设定这个结果呢
那就是设置set_value()
这是一个设置这个线程返回值的函数
成员函数 你就调用它
pormise.set_value()
调用那个真正的线程函数CalculateFactorial(n)
就把这个结果设定进行
如果这个线程发生了全部的异常
不管是哪一个 捕获它
然后set_exception()成员函数调用
把这个当前的异常重新引发
你可以在前面做一些特定的处理
然后set_exception()
做这个成员函数的调用
重新引用这个当前的异常
一设置好这个异常以后
那么就可以在f.get()或f.wait()
那个期许对象获取那个值的时候
产生这个异常的引发动作
那个时候它就可以处理这个异常
这就是承诺的全部实现