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 << " ";
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM