考虑下面的协程代码
#include <iostream>
#include <coroutine>
using namespace std;
class Resumable
{
};
Resumable func() {
cout << "hello";
co_await std::suspend_always();
cout << " world";
}
int main()
{
}
编译报错
error: unable to find the promise type for this coroutine
13 | co_await std::suspend_always();
| ^~~~~~~~
为什么?
其实编译器在编译时,会希望生成如下的代码:
/* 经过编译器优化后的 func 函数 */
Resumable func()
{
Frame *frame = operator new(size); // size = 函数形参大小 + 局部变量大小
Rumable::promise_type promise;
coroutine_handle *handle = coroutine_handle<>::from_promise(&promise);
Resumable res = promise.get_return_object(); // call the Resumable constructor
co_await promise.initial_suspend(); // in some ways, this is a coroutine constructor
try {
// func-body
cout << "hello";
co_await std::suspend_always();
cout << " world";
// func-body end
}catch (...) {
promise.unhandled_exception(); // coroutine exception handle
}
co_await promise.final_suspend(); // in some ways, this is a coroutine destructor
return res;
}
通过上面的代码,可以引出两个问题:
- 已知协程co_await可以完成上下文切换,那这个函数中co_await具体是怎么调用的?
- promise_type 哪里来?
- Resubmable如何实现?
同样,从上面的代码中可以推出,promise_type至少应该含有以下代码:
class promise_type
{
public:
auto get_return_object();
auto initial_suspend();
void unhandled_exception();
auto final_suspend();
void return_void();
};
抱着上面三个问题,看看Resumable的实现规范。
Resumable的编译实现
class Resumable
{
public: /* 用户自定义实现部分 */
class promise_type; // 见上个代码块
/*
用户的其他自定义实现代码
*/
};
解决上面提出的问题:
- 已知协程co_await可以完成上下文切换,那这个函数中co_await具体是怎么调用的?
先继续存疑
- promise_type 哪里来?
答:从 Resumable 中由用户手动定义而来,且必须实现一些特定方法。
- Resubmable如何实现?
答:Resumable 必须包含 promise_type 子类型(typedef也算),其余没什么讲究。
再提出一些新问题:
- 协程如何将一个值从函数内
co_await
到函数外? - 看起来Resumable在编译优化后的func里没有被用到,只在最后返回的时候return了一下,为什么不用promise直接代替Resumable?
换句话说:为什么要给promise加一层外套作为返回类型?C++为什么要这样设计?
以下是未解决问题列表:
- 已知协程co_await可以完成上下文切换,那这个函数中co_await具体是怎么调用的?
- C++为什么要采用给promise加一层外套作为返回类型这样的设计方式?
总结一下
从上面可以看出,co_await 之类的协程关键字依然存在,这说明此处的编译优化并不是针对协程的,那为什么要这样做呢?
答案是为了更好的管理协程,可以看到,一次小小的协程函数调用覆盖了诞生、运行、错误处理、消亡等各个部分,这为将来高可用的框架奠定了基础,但对于写hello world的人不得不说,真***复杂。
C++有一个设计规范,叫做一个人只做一件事,在这里promise_type用来管理协程的生命周期。Resumable用来作为返回值。
如果说上面讲的都是协程规范的话,那么接下来要讲的部分就是具体协程的实现,看看 co_await 到底是如何调用的?
Awaitable对象的实现规范
回到最初的起点:
Resumable func() {
cout << "hello";
co_await std::suspend_always();
cout << " world";