0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

C++ coroutine generator實(shí)現(xiàn)筆記

程序喵大人 ? 來(lái)源:程序喵大人 ? 作者:程序喵大人 ? 2022-12-15 11:03 ? 次閱讀

C++20 給我們帶來(lái)了非?,F(xiàn)代化的協(xié)程特性,但是主要的增加都集中于語(yǔ)核部分。由于庫(kù)特性尚未準(zhǔn)備充分,所以 C++20 標(biāo)準(zhǔn)庫(kù)中尚沒有多少現(xiàn)成的、組裝好的協(xié)程設(shè)施供我們使用。但是!僅僅通過使用std::coroutine_handle(這是一個(gè)編譯器為之開洞的類)并在你的類中定制好幾個(gè)規(guī)定的接口,我們就可以組合出五花八門的功能。你可以理解為,雖然我們沒有現(xiàn)成的飛機(jī)、火車,但是我們有沙子和鐵礦石!完全可以從沙子和鐵礦石出發(fā),造出飛機(jī)、火車。我知道很多人詬病 C++ 的這個(gè)特點(diǎn),沒有現(xiàn)成的這個(gè)、現(xiàn)成的那個(gè),自己造很麻煩。但是這也是我比較喜歡 C++ 的一點(diǎn)——上限非常高,你可以為自己的飛機(jī)、火車做定制,加上你想要的功能或去掉你不想要的功能;除此以外,你甚至還可以造出之前還沒有問世的東西,比如星艦!在其他語(yǔ)言中,語(yǔ)言和標(biāo)準(zhǔn)庫(kù)給你提供了什么就是什么了,你幾乎沒有超越的能力。

在 C++23 周期,LEWG (庫(kù)特性工作組) 在新冠肆虐的艱難背景下完成了大量的工作,使得 C++23 增添了不少很有益的設(shè)施(可參考 C++23特性總結(jié) - 上 - Mick235711的文章 - 知乎 https://zhuanlan.zhihu.com/p/562383157)。但是,對(duì)于協(xié)程方面的內(nèi)容還是舉棋不定。std::generator和std::lazy在合并的計(jì)劃里進(jìn)進(jìn)出出,而最終只有std::generator達(dá)成目標(biāo)。對(duì)于更花花綠綠的協(xié)程庫(kù),像task等,則可憐的連提案都沒有。

另外,在可預(yù)見的將來(lái),哪怕標(biāo)準(zhǔn)庫(kù)收錄了一些基本的協(xié)程類,為了探索更加花花綠綠的協(xié)程高級(jí)功能,我們還是需要從最基本的協(xié)程設(shè)施出發(fā),也就是理解std::coroutine_handle和相應(yīng)的接口。

本文將向讀者展示如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單的生成器 (generator) 和一個(gè)能支持協(xié)程內(nèi)外雙向信息傳遞的生成器。網(wǎng)上關(guān)于什么是協(xié)程的講解很多,哪怕是講 C++ 協(xié)程的中文材料也不少。且因筆者時(shí)間精力有限,本文不會(huì)詳細(xì)地介紹協(xié)程概念,而是更側(cè)重于展示怎么實(shí)現(xiàn)出一個(gè) generator,以填補(bǔ)相關(guān)參考資料的不足。

1

感性的了解一下協(xié)程

協(xié)程可以簡(jiǎn)單地理解為:一個(gè)執(zhí)行時(shí)可以主動(dòng)打斷,程序執(zhí)行流程可以在調(diào)用方和被調(diào)用方之間進(jìn)進(jìn)出出若干次的函數(shù)。

Python 是最早的一批支持協(xié)程的語(yǔ)言,我們不妨用 Python 來(lái)演示一下協(xié)程的神奇。(其實(shí)早在 19 年,那時(shí) C++ 編譯器還沒支持協(xié)程的時(shí)候,筆者就是利用 Python 來(lái)理解協(xié)程的)

c8e7a282-7c19-11ed-8abf-dac502259ad0.jpg

從這個(gè)例子我們可以看出以下幾點(diǎn)怪異的現(xiàn)象:

1)myrange函數(shù)中并沒有一句return語(yǔ)句,我們卻調(diào)用myrange函數(shù)得到了一個(gè)gen對(duì)象(第 22 行)

2) 22 行調(diào)用myrange函數(shù)后,這個(gè)函數(shù)似乎并沒有立即開始執(zhí)行(沒有執(zhí)行第 3 行的print語(yǔ)句,倒是執(zhí)行第 23 行的print語(yǔ)句了)

3) 調(diào)用gen對(duì)象的__next__方法后,myrange函數(shù)開始執(zhí)行。執(zhí)行到第 7 行時(shí),myrange函數(shù) "yield" 了一個(gè)值,然后程序的執(zhí)行流程又切換到主函數(shù)的第 24 行。__next__方法得到了剛剛 "yield" 的結(jié)果。

4) 26 行再次調(diào)用__next__時(shí),執(zhí)行流程回到了myrange中。而且并不是從myrange的開頭重新開始執(zhí)行,而是從上一次 "yield" 的地方,也就是第 7 行繼續(xù)執(zhí)行。i 的值似乎也沒受到影響。

如果你熟悉了協(xié)程的特點(diǎn),這無(wú)非可以概括為,協(xié)程執(zhí)行時(shí)可以主動(dòng)打斷(更學(xué)術(shù)一點(diǎn)叫掛起)自己,將控制權(quán)交還給調(diào)用方。協(xié)程掛起期間,協(xié)程的棧上信息都可以得到保留。協(xié)程恢復(fù)后,從上一次的掛起點(diǎn)繼續(xù)執(zhí)行。

經(jīng)過封裝后的 C++ 協(xié)程庫(kù),也可以向用戶展示出和 Python 中幾乎完全一致的用法。如下就是我們將要實(shí)現(xiàn)的generator所展示的應(yīng)用效果。

c8f7c4b4-7c19-11ed-8abf-dac502259ad0.jpg

當(dāng)然,C++ 畢竟是一個(gè)靜態(tài)類型的語(yǔ)言,除了range函數(shù)要寫 “返回值”generator(當(dāng)然實(shí)際上range是一個(gè)協(xié)程,代碼 81 行只是借用了原來(lái)的函數(shù)和返回值語(yǔ)法了,嚴(yán)格來(lái)說(shuō)不能認(rèn)為這里聲明了一個(gè)返回generator類型的函數(shù)) ,以及 90 行需要聲明gen的類型以外, 可以說(shuō)和 Python 沒什么兩樣了。

2

std::coroutine_handle

std::coroutine_handle類模板是為我們實(shí)現(xiàn)協(xié)程的各種“魔法”提供支持的最底層的設(shè)施,其主要負(fù)責(zé)存儲(chǔ)協(xié)程的句柄。它分為模板std::coroutine_handle和模板特化std::coroutine_handle,std::coroutine_handle可以簡(jiǎn)單理解為就是做了類型擦除的std::coroutine_handle

打開頭文件我們不難看出,std::coroutine_handle中的不少方法是依靠 __builtin 內(nèi)建函數(shù),也就是編譯器開洞實(shí)現(xiàn)的。

c9117f76-7c19-11ed-8abf-dac502259ad0.jpgc939f500-7c19-11ed-8abf-dac502259ad0.jpg

std::coroutine_handle中保存了協(xié)程的上下文。我們協(xié)程執(zhí)行到哪兒切出去了(協(xié)程切換回來(lái)后從哪兒開始繼續(xù)執(zhí)行)?我們協(xié)程的棧上變量在協(xié)程切出去期間怎么能得到保留?這些問題的解決都是歸功于std::coroutine_handle保存了協(xié)程的上下文。

std::coroutine_handle中的方法不多,但是各個(gè)都至關(guān)重要。由于有些概念還沒有鋪墊,我們先只羅列三個(gè)比較容易理解的方法:

done方法,可以查詢一個(gè)協(xié)程是否已經(jīng)結(jié)束;

resume方法可以恢復(fù)一個(gè)協(xié)程的執(zhí)行;

destroy方法可以銷毀一個(gè)協(xié)程。

std::coroutine_handle只是一個(gè)很底層很底層的設(shè)施,沒有 RAII 包裹。它就像裸指針一樣(其實(shí)它內(nèi)部也就是一個(gè)裸指針),需要靠我們手動(dòng)創(chuàng)建、手動(dòng)銷毀。我們剛剛談到,std::coroutine_handle保存了協(xié)程的上下文,其中就有棧上變量的信息。如果一個(gè) handle 被創(chuàng)建出來(lái),用完以后我們忘了對(duì)它調(diào)用 destroy 了,那么其中存儲(chǔ)的上下文信息當(dāng)然也就沒有被銷毀——也就是內(nèi)存泄漏了。如果不小心做了兩次 destroy,那么就可能會(huì)引發(fā) double free 錯(cuò)誤了。所以,我們得寫一個(gè) RAII 類將其包裝起來(lái),以解決忘記銷毀或者其他比如淺拷貝等問題。

這里,鄭重向大家推薦由清華大學(xué)出版社出版的《C++20 實(shí)踐入門》和《C++20 高級(jí)編程》。這兩本書是目前最新的一批介紹 C++20 的教程。該書緊跟潮流,就 C++20 的幾大熱點(diǎn)內(nèi)容,如modules、concepts、ranges等作了詳細(xì)介紹。全卷內(nèi)容質(zhì)量上乘,精雕細(xì)琢,非那些在歷史舊版本的基礎(chǔ)上草草加兩章節(jié)新內(nèi)容圈錢的書可比也!

非常感謝清華大學(xué)出版社對(duì)這篇文章的贊助!本著對(duì)我的讀者負(fù)責(zé)的態(tài)度,我堅(jiān)持要求審讀完書的大部分內(nèi)容后才能做推薦,清華大學(xué)出版社的編輯對(duì)此給予了高度支持。且,從 8 月份聯(lián)系我開始,到本文落筆,編輯非常寬容地給了我 4 個(gè)月的時(shí)間 —— 一段非常充足的時(shí)間閱讀了這兩本書,之后才有了這里的精心推薦。再次表示致敬和謝意!

3

generator

我們的generator類終于出場(chǎng)了。

首先,C++ 的協(xié)程要求generator中必須有promise_type這個(gè)類型。你可以通過typedef/using的方式 alias 一個(gè)別名,也可以干脆就定義成generator的內(nèi)部類 —— 本文選擇后者。

template 
struct generator
{
    struct promise_type
    {
    }
};

promise_type中有這么幾個(gè)可定制的、會(huì)影響協(xié)程行為的重要接口,先介紹兩個(gè):

1)initial_suspend—— 它回答的是協(xié)程一出生時(shí)是否就要馬上掛起的問題;

2)final_suspend—— 它回答的是協(xié)程最后一次是否要掛起的問題;

對(duì)于一個(gè)generator而言,這兩個(gè)問題的回答是:

初始時(shí)始終都要掛起,最后一次始終都不掛起。

std::suspend_alwaysstd::suspend_never是標(biāo)準(zhǔn)庫(kù)里面已經(jīng)定義好的類型,可以方便地回答是否要掛起的問題。

    struct promise_type
    {
        ...

        std::suspend_always initial_suspend() const
        {
            return {};
        }

        std::suspend_never final_suspend() const noexcept
        // 這里注意一下,由于 final_suspend 在收尾階段工作,所以必須是 noexcept 的
        {
            return {};
        }
    }

在新協(xié)程創(chuàng)建完畢好后,C++ 會(huì)執(zhí)行co_await promise.initial_suspend(),同樣的, 在協(xié)程結(jié)束前也會(huì)co_await promise.final_suspend()。當(dāng)然了,從名字中我們也能看出,co_await一個(gè)std::suspend_always時(shí),執(zhí)行流程永遠(yuǎn)都會(huì)無(wú)條件切出去,而對(duì)于std::suspend_never則是永遠(yuǎn)也不會(huì)切出。

還記得我們剛剛觀察的 Python 代碼中的現(xiàn)象嗎?主函數(shù)中調(diào)用myrange的時(shí)候,是不是立馬得到一個(gè)gen對(duì)象的?是不是myrange里面沒有立即得到執(zhí)行的?在第一次調(diào)用__next__的時(shí)候才會(huì)去執(zhí)行的吧?這其實(shí)就是因?yàn)閙yrange協(xié)程一創(chuàng)建好就掛起自己將程序流程切回到調(diào)用方了。

如果initial_suspend這里回答的是suspend_never,那么協(xié)程就會(huì)立刻開始執(zhí)行。

建議等generator實(shí)現(xiàn)完成后讀者自己動(dòng)手實(shí)踐下,將initial_suspend和final_suspend的回答換換,看看結(jié)果會(huì)有什么改變。

3)promise_type中第三個(gè)定制接口是unhandled_exception,它回答的是協(xié)程被其里頭的沒有捕獲的異常終止時(shí)做何處理的問題。

我們這里只是簡(jiǎn)單處理一下,調(diào)用exit提前終止程序。當(dāng)然這樣的做法太簡(jiǎn)化了,實(shí)際應(yīng)用時(shí)可以考慮使用std::exception_ptr等設(shè)施做更嚴(yán)謹(jǐn)?shù)奶幚怼?/p>

    struct promise_type
    {
        ...

        void unhandled_exception()
        {
            std::exit(EXIT_FAILURE);
        }
    }

4)promise_type中第四個(gè)定制接口,也是最核心的一個(gè)是get_return_object。這個(gè)方法也涉及到了如何創(chuàng)建一個(gè) coroutine 的問題 —— 答案就是使用std::coroutine_handle::from_promise(*this),即從自己這個(gè) promise ( 也就是*this) 創(chuàng)建一個(gè) coroutine (from_promise得到的就是一個(gè)coroutine_handle)。generator中也需要配合,提供一個(gè)接受coroutine_handle類型的構(gòu)造函數(shù),將剛剛構(gòu)造出的coroutine_handle保存。

現(xiàn)在,通過get_return_object得到了return_object,就會(huì)開始詢問是否要做initial_suspend了 (剛剛介紹的initial_suspend還記得嗎?)

template 
struct generator
{
    struct promise_type;

    std::coroutine_handle handle;

    struct promise_type
    {
        ...
        generator get_return_object()
        {
            return generator{std::coroutine_handle::from_promise(*this)};
        }
    };

    generator(std::coroutine_handle handle) :
        handle(handle)
    {
    }

    ...
};

我們之前也提到,coroutine_handle是無(wú) RAII 的,因此generator中得根據(jù)三/五法則,做好 RAII。該析構(gòu)的析構(gòu),禁止拷貝,寫好移動(dòng)構(gòu)造/移動(dòng)賦值。

template 
struct generator
{
    struct promise_type;

    std::coroutine_handle handle;

    ...

public:
    generator() = default;
    generator(const generator &) = delete;

private:
    generator(std::coroutine_handle handle) :
        handle(handle)
    {
    }

public:

    generator(generator && src) :
        handle(src.handle)
    {
        src.handle = nullptr;
    }

    generator& operator=(const generator &) = delete;

    generator& operator=(generator && src)
    {
        if (!handle) {
            handle.destroy();
        }
        handle = src.handle;
        src.handle = nullptr;
    }

    ~generator()
    {
        if (!handle) {
            handle.destroy();
        }
    }

    ...
};

5) 定制yield_value接口

接下來(lái)的定制則對(duì)于generator來(lái)說(shuō)至關(guān)重要,我們馬上就可以讓我們的generator支持 yield 了!

c9b3baac-7c19-11ed-8abf-dac502259ad0.png

還是以此舉例,co_yield關(guān)鍵字實(shí)際上只是一個(gè)語(yǔ)法糖,這一行會(huì)被編譯器替換為co_await promise.yield_value(i),在有了initial_suspend和final_suspend的經(jīng)驗(yàn)后,我們這次也就能很容易地猜測(cè)出,我們要在promise_type中實(shí)現(xiàn)一個(gè)yield_value方法,而返回值負(fù)責(zé)回答要不要切出的問題。顯然,每次 yield 時(shí)總是要掛起協(xié)程,所以,yield_value方法的返回值類型應(yīng)當(dāng)是suspend_always。你猜對(duì)了嗎?

    struct promise_type
    {
        ....

        std::optional opt;

        template 
        std::suspend_always yield_value(Arg && arg)
        {
            opt.emplace(std::forward(arg));
            return {};
        }
    };

在promise中,我們還增加了一個(gè)optional,用以存放 yield 的結(jié)果。注意,很多例子,甚至包括 cppreference 在內(nèi),promise內(nèi)部都是用的T類型的裸值來(lái)存放 yield 的結(jié)果的。在模板編程中這樣做兼容性不太好,我們需要考慮能照顧到不可默認(rèn)構(gòu)造的類型。除此以外,我們使用萬(wàn)能引用和完美轉(zhuǎn)發(fā)以提升構(gòu)造值時(shí)的性能。

而這個(gè)opt,當(dāng)然是在等generator來(lái)取它的。

template 
struct generator
{
    ...

    T & next()
    {
        handle.resume();
        if (handle.done()) {
            throw generator_done("generator done");
        }
        return *(handle.promise().opt);
    }
};

generator range(int n)
{
    for(int i = 0; i < n; ++i) {
        co_yield i;
    }
}

int main()
{
    generator gen = range(4);

    for (int i = 0; i < 4; ++i) {
        std::cout << gen.next() << std::endl;
    }
}

這里需要結(jié)合前文介紹過的內(nèi)容梳理下。由于initial_suspend的返回值是suspend_always,所以協(xié)程剛創(chuàng)建好后就切出,執(zhí)行流程到了gen = range(4)。

再下面,每次對(duì)gen調(diào)用next方法時(shí),會(huì)執(zhí)行handle.resume()恢復(fù)協(xié)程。

協(xié)程首次恢復(fù)運(yùn)行,當(dāng)然是從range函數(shù)的開頭開始執(zhí)行 (如果不是首次恢復(fù)運(yùn)行,當(dāng)然就是從上一次 yield 出去的地方恢復(fù)運(yùn)行),直到碰上了co_yield。這時(shí), 調(diào)用promise.yield_value(i),根據(jù)co_yield后面值 (也就是i) 構(gòu)造好了值保存在opt中。隨后,由于promise.yield_value(i)的結(jié)果是suspend_always,所以協(xié)程切出, 執(zhí)行流程回到了handle.resume()之后。正常情況下 (協(xié)程沒有執(zhí)行完畢),next方法就會(huì)從promise里的那個(gè)optional中取出 yield 的結(jié)果,返回給主函數(shù)中以供輸出。如果檢測(cè)到已經(jīng)是最后一次 yield 后再調(diào)用next的 (即 resume 后檢測(cè)到 done 的話),則拋出generator_done異常。

完整的generator代碼如下:

#include 
#include 
#include 
#include 
#include 

struct generator_done :
        std::logic_error
{
    private:
        typedef std::logic_error super;
    
    public:
        using super::super;
};

template 
struct generator
{
    struct promise_type;

    std::coroutine_handle handle;

    struct promise_type
    {
        std::optional opt;

        std::suspend_always initial_suspend() const
        {
            return {};
        }

        std::suspend_never final_suspend() const noexcept
        {
            return {};
        }

        void unhandled_exception()
        {
            std::exit(EXIT_FAILURE);
        }

        generator get_return_object()
        {
            return generator{std::coroutine_handle::from_promise(*this)};
        }

        template 
        std::suspend_always yield_value(Arg && arg)
        {
            opt.emplace(std::forward(arg));
            return {};
        }
    };

public:
    generator() = default;
    generator(const generator &) = delete;

private:
    generator(std::coroutine_handle handle) :
        handle(handle)
    {
    }

public:
    generator(generator && src) :
        handle(src.handle)
    {
        src.handle = nullptr;
    }

    generator& operator=(const generator &) = delete;

    generator& operator=(generator && src)
    {
        if (!handle) {
            handle.destroy();
        }
        handle = src.handle;
        src.handle = nullptr;
    }


    ~generator()
    {
        if (!handle) {
            handle.destroy();
        }
    }

    T & next()
    {
        handle.resume();
        if (handle.done()) {
            throw generator_done("generator done");
        }
        return *(handle.promise().opt);
    }

};

generator range(int n)
{
    for(int i = 0; i < n; ++i) {
        co_yield i;
    }
}

int main ()
{
    generator gen = range(5);

    for (int i = 0; i < 5; ++i) {
        std::cout << gen.next() << std::endl;
    }

}

4

能雙向傳遞信息的

bigenerator

我們目前完成的generator只能做到協(xié)程內(nèi)部向外部 yield 一個(gè)值,傳遞出來(lái)信息。能不能做到外部也向協(xié)程內(nèi)部回復(fù)一個(gè)值,將信息由外部傳遞到協(xié)程內(nèi)部呢?C++ 的協(xié)程機(jī)制也是允許的。

其實(shí),co_yield表達(dá)式,當(dāng)然,我們上面也知道了, 其實(shí)就是co_await promise.yield_value()這個(gè)表達(dá)式,其實(shí)也是有計(jì)算結(jié)果的,只不過,我們之前generator中的例子,計(jì)算結(jié)果為void類型 —— 沒有返回值罷了。

要想整個(gè)表達(dá)式有返回值,當(dāng)然我們得從promise.yield_value()的返回值入手。我們以前用的是std::suspend_always,現(xiàn)在得自己配置了。

先上效果:

c9db2380-7c19-11ed-8abf-dac502259ad0.jpg

再上源碼:

#include 
#include 
#include 
#include 
#include 

struct generator_done :
        std::logic_error
{
    private:
        typedef std::logic_error super;
    
    public:
        using super::super;
};

template 
struct bigenerator
{
    struct promise_type;

    std::coroutine_handle handle;


    struct awaitable : public std::suspend_always
    {
        std::variant * ref;

        U & await_resume() const
        {
            return std::get(*ref);
        }
    };


    struct promise_type
    {
        std::variant var;

        std::suspend_always initial_suspend() const
        {
            return {};
        }

        std::suspend_never final_suspend() const noexcept
        {
            return {};
        }

        void unhandled_exception()
        {
            std::exit(EXIT_FAILURE);
        }

        bigenerator get_return_object()
        {
            return bigenerator{std::coroutine_handle::from_promise(*this)};
        }

        template 
        awaitable yield_value(Arg && arg)
        {
            var.template emplace(std::forward(arg));
            return awaitable{.ref = &var};
        }
    };

public:
    bigenerator() = default;
    bigenerator(const bigenerator &) = delete;

private:
    bigenerator(std::coroutine_handle handle) :
        handle(handle)
    {
    }

public:
    bigenerator(bigenerator && src) :
        handle(src.handle)
    {
        src.handle = nullptr;
    }

    bigenerator& operator=(const bigenerator &) = delete;

    bigenerator& operator=(bigenerator && src)
    {
        if (!handle) {
            handle.destroy();
        }
        handle = src.handle;
        src.handle = nullptr;
    }


    ~bigenerator()
    {
        if (!handle) {
            handle.destroy();
        }
    }

    template 
    T & next(Args&& ... args)
    {
        handle.promise().var.template emplace(std::forward(args)...);
        handle.resume();
        if (handle.done()) {
            throw generator_done("generator done");
        }
        return std::get(handle.promise().var);
    }

};

bigenerator range(int n)
{
    for(int i = 0; i < n; ++i) {
        std::string sunk = co_yield i;
        std::cout << sunk << std::endl;
    }
}

int main ()
{
    bigenerator gen = range(10);

    for (int i = 0; i < 5; ++i) {
        std::cout << gen.next(i + 1, 'a') << std::endl;
    }
}
 

然后講解:

主要變動(dòng)就是一個(gè)新的內(nèi)部類:awaitable,在bigenerator中,yield_value接口返回的就是這個(gè)類型。它繼承自std::suspend_always,表明我們確實(shí)還是需要每次 yield 時(shí)都要掛起,但是,這里我們重寫了await_resume方法,使得協(xié)程在恢復(fù)時(shí)調(diào)用這個(gè)方法,從 promise 中取出外界傳遞進(jìn)去的結(jié)果。

    struct awaitable : public std::suspend_always
    {
        std::variant * ref;

        U & await_resume() const
        {
            return std::get(*ref);
        }
    };

下面的代碼片段展示了yield_value中怎么構(gòu)造awaitable。其實(shí)只要告知 promise 中的variant的地址就可以了。bigenerator中改用了variant,主要是考慮到 yield 出去的值和 resume 時(shí)傳遞進(jìn)來(lái)的值不會(huì)在同一時(shí)刻存在,使用variant有助于節(jié)省空間。

    struct promise_type
    {
        ...

        std::variant var;

        template 
        awaitable yield_value(Arg && arg)
        {
            var.template emplace(std::forward(arg));
            return awaitable{.ref = &var};
        }
    };

還有bigenerator中next的變化,其實(shí)也就是恢復(fù)協(xié)程前,在 promise 的variant中構(gòu)造好傳進(jìn)去的對(duì)象就好了。

template 
struct bigenerator
{
    ...

    template 
    T & next(Args&& ... args)
    {
        handle.promise().var.template emplace(std::forward(args)...);
        handle.resume();
        if (handle.done()) {
            throw generator_done("generator done");
        }
        return std::get(handle.promise().var);
    }
};

當(dāng)然,我們這里沒有考慮到bigenerator這種傳出來(lái)和傳進(jìn)去的消息類型都一樣的情況,但其實(shí)只要做一下偏特化就可以了。由于不是協(xié)程部分的技術(shù)難點(diǎn),就不再多介紹了。

全文完。

審核編輯 :李倩

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • C++
    C++
    +關(guān)注

    關(guān)注

    21

    文章

    2090

    瀏覽量

    73404
  • 編譯器
    +關(guān)注

    關(guān)注

    1

    文章

    1607

    瀏覽量

    48977
  • 生成器
    +關(guān)注

    關(guān)注

    7

    文章

    313

    瀏覽量

    20921

原文標(biāo)題:C++ coroutine generator 實(shí)現(xiàn)筆記

文章出處:【微信號(hào):程序喵大人,微信公眾號(hào):程序喵大人】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    C++語(yǔ)言基礎(chǔ)知識(shí)

    電子發(fā)燒友網(wǎng)站提供《C++語(yǔ)言基礎(chǔ)知識(shí).pdf》資料免費(fèi)下載
    發(fā)表于 07-19 10:58 ?6次下載

    C++實(shí)現(xiàn)類似instanceof的方法

    函數(shù),可實(shí)際上C++中沒有。但是別著急,其實(shí)C++中有兩種簡(jiǎn)單的方法可以實(shí)現(xiàn)類似Java中的instanceof的功能。 在 C++ 中,確定對(duì)象的類型是編程中實(shí)際需求,使開發(fā)人員
    的頭像 發(fā)表于 07-18 10:16 ?435次閱讀
    <b class='flag-5'>C++</b>中<b class='flag-5'>實(shí)現(xiàn)</b>類似instanceof的方法

    C/C++中兩種宏實(shí)現(xiàn)方式

    #ifndef的方式受C/C++語(yǔ)言標(biāo)準(zhǔn)支持。它不僅可以保證同一個(gè)文件不會(huì)被包含多次,也能保證內(nèi)容完全相同的兩個(gè)文件(或者代碼片段)不會(huì)被不小心同時(shí)包含。
    的頭像 發(fā)表于 04-19 11:50 ?505次閱讀

    鴻蒙OS開發(fā)實(shí)例:【Native C++

    使用DevEco Studio創(chuàng)建一個(gè)Native C++應(yīng)用。應(yīng)用采用Native C++模板,實(shí)現(xiàn)使用NAPI調(diào)用C標(biāo)準(zhǔn)庫(kù)的功能。使用C
    的頭像 發(fā)表于 04-14 11:43 ?2347次閱讀
    鴻蒙OS開發(fā)實(shí)例:【Native <b class='flag-5'>C++</b>】

    使用 MISRA C++:2023? 避免基于范圍的 for 循環(huán)中的錯(cuò)誤

    在前兩篇博客中,我們?向您介紹了新的 MISRA C++ 標(biāo)準(zhǔn)?和?C++ 的歷史?。在這篇博客中,我們將仔細(xì)研究以 C++ 中?for?循環(huán)為中心的特定規(guī)則。
    的頭像 發(fā)表于 03-28 13:53 ?657次閱讀
    使用 MISRA <b class='flag-5'>C++</b>:2023? 避免基于范圍的 for 循環(huán)中的錯(cuò)誤

    c語(yǔ)言,c++,java,python區(qū)別

    C語(yǔ)言、C++、Java和Python是四種常見的編程語(yǔ)言,各有優(yōu)點(diǎn)和特點(diǎn)。 C語(yǔ)言: C語(yǔ)言是一種面向過程的編程語(yǔ)言。它具有底層的特性,能夠?qū)τ?jì)算機(jī)硬件進(jìn)行直接操作。
    的頭像 發(fā)表于 02-05 14:11 ?1633次閱讀

    C++簡(jiǎn)史:C++是如何開始的

    MISRA C++:2023,MISRA? C++ 標(biāo)準(zhǔn)的下一個(gè)版本,來(lái)了!為了幫助您做好準(zhǔn)備,我們介紹了 Perforce 首席技術(shù)支持工程師 Frank van den Beuken 博士撰寫
    的頭像 發(fā)表于 01-11 09:00 ?485次閱讀
    <b class='flag-5'>C++</b>簡(jiǎn)史:<b class='flag-5'>C++</b>是如何開始的

    C語(yǔ)言和C++中那些不同的地方

    C語(yǔ)言雖說(shuō)經(jīng)常和C++在一起被大家提起,但可千萬(wàn)不要以為它們是一個(gè)東西?,F(xiàn)在我們常用的C語(yǔ)言是C89標(biāo)準(zhǔn),C++
    的頭像 發(fā)表于 12-07 14:29 ?850次閱讀
    <b class='flag-5'>C</b>語(yǔ)言和<b class='flag-5'>C++</b>中那些不同的地方

    開箱即用!教你如何正確使用華為云CodeArts IDE for C/C++

    C/C++編碼體驗(yàn)、方便的訪問華為云資源、簡(jiǎn)單的引用華為云服務(wù)于一身,實(shí)現(xiàn)C/C++開發(fā)者在個(gè)人研發(fā)作業(yè)體驗(yàn)和效率上的巨大提升。 為了幫助
    的頭像 發(fā)表于 11-29 17:40 ?693次閱讀
    開箱即用!教你如何正確使用華為云CodeArts IDE for <b class='flag-5'>C</b>/<b class='flag-5'>C++</b>!

    如何選擇創(chuàng)建c語(yǔ)言和c++

    選擇創(chuàng)建 C 語(yǔ)言和 C++ 都需要綜合考慮多個(gè)因素。在決定使用哪種語(yǔ)言之前,我們需要對(duì)這兩種語(yǔ)言的特點(diǎn)、優(yōu)缺點(diǎn)、適用場(chǎng)景、學(xué)習(xí)成本等進(jìn)行全面的了解和對(duì)比。下面是關(guān)于選擇創(chuàng)建 C 語(yǔ)言和 C+
    的頭像 發(fā)表于 11-27 15:58 ?527次閱讀

    c++怎么開始編程

    C++是一種高級(jí)的、通用的編程語(yǔ)言,用于開發(fā)各種類型的應(yīng)用程序。它是從C語(yǔ)言演變而來(lái),也是一種靜態(tài)類型語(yǔ)言,可以在不同的平臺(tái)上進(jìn)行開發(fā)。C++具有高度的靈活性和性能,并且廣泛應(yīng)用于游戲開發(fā)、桌面
    的頭像 發(fā)表于 11-27 15:56 ?819次閱讀

    c++多行注釋快捷鍵

    C++中,多行注釋(也稱為塊注釋)是一種用于注釋大段代碼或多個(gè)語(yǔ)句的方法。當(dāng)你希望暫時(shí)禁用一些代碼或者解釋特定部分代碼的作用時(shí),多行注釋是非常有用的。 在C++中,多行注釋以 /* 開始,以
    的頭像 發(fā)表于 11-22 10:24 ?7486次閱讀

    C/C++語(yǔ)言學(xué)習(xí)大全套

    電子發(fā)燒友網(wǎng)站提供《C/C++語(yǔ)言學(xué)習(xí)大全套.rar》資料免費(fèi)下載
    發(fā)表于 11-18 14:33 ?2次下載
    <b class='flag-5'>C</b>/<b class='flag-5'>C++</b>語(yǔ)言學(xué)習(xí)大全套

    C++智能指針的底層實(shí)現(xiàn)原理

    C++智能指針的頭文件: #include 1. shared_ptr: 智能指針從本質(zhì)上來(lái)說(shuō)是一個(gè)模板類,用類實(shí)現(xiàn)對(duì)指針對(duì)象的管理。 template class shared_ptr
    的頭像 發(fā)表于 11-09 14:32 ?641次閱讀
    <b class='flag-5'>C++</b>智能指針的底層<b class='flag-5'>實(shí)現(xiàn)</b>原理

    C++之父新作帶你勾勒現(xiàn)代C++地圖

    為了幫助大家解決這些痛點(diǎn)問題,讓大家領(lǐng)略現(xiàn)代C++之美,掌握其中的精髓,更好地使用C++,C++之父Bjarne Stroustrup坐不住了,他親自操刀寫就了這本《C++之旅》!
    的頭像 發(fā)表于 10-30 16:35 ?746次閱讀
    <b class='flag-5'>C++</b>之父新作帶你勾勒現(xiàn)代<b class='flag-5'>C++</b>地圖