C++20协程解糖 - 动手实现协程3 - generator和co_yield
本期实现的功能很简单,协程的重头都在co_await和异步操作上,generator本身是一个很轻的东西
如果你看到这行文字,说明这篇文章被无耻的盗用了(或者你正在选中文字),请前往 cnblogs.com/pointer-smq 支持原作者,谢谢
基本结构
generator和future/promise的模式的区别在于,future/promise的核心数据存在堆分配的shared_state里,协程对象(promise)、future共享引用shared_state,协程由异步操作的完成回调推动,协程完成后自行销毁;而generator模式中,核心数据存在promise中,generator唯一引用协程对象(promise),协程由外部for循环使用者推动,generator控制协程销毁。
一些设计要点
- 协程启动后即暂停(initial_suspend)
- 协程结束前暂停(final_suspend),由generator析构控制协程destroy
- operator++负责控制协程恢复执行
- generator唯一引用协程,应当禁止拷贝
- generator的迭代器是input迭代器
一些需要注意的地方
- generator只能迭代一遍
- 销毁generator会使迭代器失效
- 迭代器越界++会导致严重的UB(resume已经销毁的协程)
- 拷贝的迭代器++其中一个,另一个的状态也会变化
开始写代码
首先是generator的框架和对应的promise_type
template<class T>
class Generator {
struct Promise {
exp::suspend_always initial_suspend() { return {}; }
exp::suspend_always final_suspend() noexcept { return {}; }
Generator<T> get_return_object() { return {this}; }
void unhandled_exception() { std::terminate(); }
exp::suspend_always yield_value(T v) {
_current = std::move(v);
return {};
}
void return_void() {}
T _current;
};
public:
using promise_type = Promise;
Generator(const Generator& other) = delete;
Generator& operator=(const Generator& other) = delete;
Generator(Generator&& other) noexcept
: _promise(other._promise) {
other._promise = nullptr;
}
Generator& operator=(Generator&& other) noexcept = delete;
~Generator() {
if (_promise) {
exp::coroutine_handle<Promise>::from_promise(*_promise).destroy();
}
}
private:
Generator(Promise* sink)
: _promise(sink)
{}
Promise* _promise;
};
Generator强引用_promise,禁用拷贝,允许移动,移动赋值也应该允许的,但是太麻烦了索性delete了。
Generator析构函数里,通过promise指针拿到promise对应的协程,并调用destroy销毁。
通过co_yield返回的对象通过Primise::yield_value存储在Promise内部,等待Generator的迭代器来取出;yield_value返回suspend_always,表示每次yield之后协程都暂停,等下一次推动
这里面还差推动协程resume的iterator,现在补上
如果你看到这行文字,说明这篇文章被无耻的盗用了(或者你正在选中文字),请前往 cnblogs.com/pointer-smq 支持原作者,谢谢
template<class T>
class Generator {
// ...
// public
struct iterator_end_sentinel {};
struct iterator {
template<class>
friend class Generator;
using iterator_category = std::input_iterator_tag;
using value_type = T;
T operator*() {
return _promise->_current;
}
void operator++() {
exp::coroutine_handle<Promise>::from_promise(*_promise).resume();
}
bool operator!=(iterator_end_sentinel) {
return !exp::coroutine_handle<Promise>::from_promise(*_promise).done();
}
private:
iterator(Promise* promise)
: _promise(promise) {
}
Promise* _promise;
};
iterator begin() { return {_promise}; }
iterator_end_sentinel end() { return {}; }
// ...
};
这里begin和end返回的不是同一个迭代器类型,是C++17允许的来着?
iterator弱引用_promise。
operator*返回_promise中的当前值
operator++从_promise获取coroutine_handle,使用resume恢复执行
operator!=(iterator_end_sentinel)从_promise获取coroutine_handle,并判断是否done(当协程暂停在final_suspend时即为done)
齐活了,就这么简单,赶紧自己写一个吧!
附录
完整代码
如果你看到这行文字,说明这篇文章被无耻的盗用了(或者你正在选中文字),请前往 cnblogs.com/pointer-smq 支持原作者,谢谢
#include <iostream>
#include <experimental/coroutine>
namespace exp = std::experimental;
template<class T>
class Generator {
struct Promise {
exp::suspend_always initial_suspend() { return {}; }
exp::suspend_always final_suspend() noexcept { return {}; }
Generator<T> get_return_object() { return {this}; }
void unhandled_exception() { std::terminate(); }
exp::suspend_always yield_value(T v) {
_current = std::move(v);
return {};
}
void return_void() {}
T _current;
};
public:
using promise_type = Promise;
struct iterator_end_sentinel {};
struct iterator {
template<class>
friend class Generator;
using iterator_category = std::input_iterator_tag;
using value_type = T;
T operator*() {
return _promise->_current;
}
void operator++() {
exp::coroutine_handle<Promise>::from_promise(*_promise).resume();
}
bool operator!=(iterator_end_sentinel) {
return !exp::coroutine_handle<Promise>::from_promise(*_promise).done();
}
private:
iterator(Promise* promise)
: _promise(promise) {
}
Promise* _promise;
};
iterator begin() { return {_promise}; }
iterator_end_sentinel end() { return {}; }
Generator(const Generator& other) = delete;
Generator& operator=(const Generator& other) = delete;
Generator(Generator&& other) noexcept
: _promise(other._promise) {
other._promise = nullptr;
}
Generator& operator=(Generator&& other) noexcept = delete;
~Generator() {
if (_promise) {
exp::coroutine_handle<Promise>::from_promise(*_promise).destroy();
}
}
private:
Generator(Promise* sink)
: _promise(sink)
{}
Promise* _promise;
};
Generator<int> func() {
for(int i = 0; i < 10; i++) {
co_yield i;
}
}
int main() {
for (int i : func()) {
std::cout << i << " ";
}
}