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控制协程销毁。

图片1

一些设计要点

  • 协程启动后即暂停(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 << " ";
    }
}

原文链接: https://www.cnblogs.com/pointer-smq/p/12950668.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

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

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/197792

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月12日 下午7:41
下一篇 2023年2月12日 下午7:41

相关推荐