码迷,mamicode.com
首页 > 编程语言 > 详细

C++20协程解糖 - 动手实现协程3 - generator和co_yield

时间:2020-05-24 13:59:39      阅读:102      评论:0      收藏:0      [点我收藏+]

标签:lin   col   弱引用   har   struct   结构   作者   this   sentinel   

本期实现的功能很简单,协程的重头都在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 << " ";
    }
}

C++20协程解糖 - 动手实现协程3 - generator和co_yield

标签:lin   col   弱引用   har   struct   结构   作者   this   sentinel   

原文地址:https://www.cnblogs.com/pointer-smq/p/12950668.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!