• C++20协程解糖


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

  • 相关阅读:
    P4387 P4387 【深基15.习9】验证栈序列
    P1241 括号序列题解
    P2058 海港题解
    P1540 机器翻译题解
    leaflet + react + typescript
    TypeScript中文手册:从 JavaScript 迁移到 TypeScript
    react-esri-leaflet与typescript
    TypeError: Super expression must either be null or a function
    前端库(gis前端库和普通库分开)
    react-leaflet:Module parse failed: Unexpected token (10:41)
  • 原文地址:https://www.cnblogs.com/pointer-smq/p/12950668.html
Copyright © 2020-2023  润新知