保钓者 發表於 2025-6-18 10:37:00

协程本质是函数加状态机——零基础深入浅出 C++20 协程

<h1>前言</h1>
<p><span style="font-size: 18px">之前写过一篇 C++20 协程入门的文章:《<span role="heading" aria-level="2">使用 C++ 20 协程降低异步网络编程复杂度</span></span><span style="font-size: 18px">》,谈到了协程在消除异步编程 callback hell 方面的重要贡献——使代码更清晰,易于维护;以及 C++20 协程具有无栈、非对称等特性。<strong>无栈协程</strong>具有不受预分配栈空间约束、切换类似函数开销更小的优点,符合 C++ 语言设计原则中的 no payload 理念 (不因新增加的语言特性而增加额外性能负担);<strong>非对称</strong>表示协程控制权的转移是单向的,即通过 co_await/co_yield 挂起时,必需返回到调用者最初的上下文,而不能随意切换到其它协程,这样做逻辑清晰,便于调试。</span></p>
<p><span style="font-size: 18px">C++20 协程相对的缺点就是概念繁多、过于灵活,特别是编译器在底层默默的做了很多工作,使得调用链经常断掉不好理解,之前的文章讲到原理就草草贴了几张流程图了事,今天要把这个原理掰开了好好说道一番。</span></p>
<p><span style="font-size: 18px">讲 C++20 协程,除了协程本身的复杂性,还有新标准带来的新特性,每次新的标准面世,就像是换了个语言,各种语法糖能大大提升开发效率,但也提升了理解成本。</span><span style="font-size: 18px">以插入 map 元素这个小功能为例,看看各个标准是如何演化的。</span></p>
<p><span style="font-size: 18px">我们知道,std::map 在 insert 时如果元素已经存在是不会替换元素的,而是返回一个指示元素所在位置的 iterator 和是否插入成功的标志:</span></p>
<pre class="language-cpp highlighter-hljs"><code>#include &lt;iostream&gt;
#include &lt;map&gt;

int main() {
    std::map&lt;int, int&gt; mp;
    // mp.insert(std::make_pair(1, 2));
    std::pair&lt;std::map&lt;int, int&gt;::iterator, bool&gt; result = mp.insert(std::make_pair(1, 1));
    if (result.second)
      std::cout &lt;&lt; "inserted" &lt;&lt; std::endl;

    for (std::map&lt;int, int&gt;::iterator itr = mp.begin(); itr != mp.end(); ++itr) {
      std::cout &lt;&lt; "{" &lt;&lt; itr-&gt;first &lt;&lt; ", " &lt;&lt; itr-&gt;second &lt;&lt; "}" &lt;&lt; std::endl;
    }

    return 0;
}</code></pre>
<p><span style="font-size: 18px">输出:</span></p>
<pre class="language-bash highlighter-hljs"><code>inserted
{1, 1}</code></pre>
<p><span style="font-size: 18px">这是 C++98 标准就支持的语法,map::insert 返回值为 std::pair,其 first 为容器 iterator 用于标识插入或已有元素位置,其 second 为 bool 表示是否插入成功。下面看下 C++11 的改进:</span></p>
<pre class="language-cpp highlighter-hljs"><code>#include &lt;iostream&gt;
#include &lt;map&gt;
#include &lt;tuple&gt;

int main() {
    std::map&lt;int, int&gt; mp; // = { {1,3} };
    bool inserted;
    std::tie(std::ignore, inserted) = mp.insert({1, 1});
    if (inserted)
      std::cout &lt;&lt; "inserted" &lt;&lt; std::endl;

    // for (auto itr = mp.begin(); itr != mp.end(); ++itr) {
    for(auto itr : mp) {
      std::cout &lt;&lt; "{" &lt;&lt; itr.first &lt;&lt; ", " &lt;&lt; itr.second &lt;&lt; "}" &lt;&lt; std::endl;
    }
    return 0;
}</code></pre>
<p><span style="font-size: 18px">输出一致。主要改进在于通过 tie 将 inserted 变量绑定到返回的 tuple 结构中 (pair 也是 tuple 的一种),之后直接引用 inserted 变量,而不是不明就里的 first &amp; second,代码可读性更强了并且没有额外的对象拷贝。</span><span style="font-size: 18px">这个 demo 还展示了 C++11 引入的其它特性,如:</span></p>
<p><span style="font-size: 18px">* 聚合初始化 :<code>std::map&lt;int, int&gt; mp; // = { {1,1} };&nbsp;</code> &amp; <code>mp.insert({1, 1});</code><br></span></p>
<p><span style="font-size: 18px">* 类型自动推导:<code>// for (auto itr = mp.begin(); itr != mp.end(); ++itr)</code></span></p>
<p><span style="font-size: 18px">* 范围 for 循环:<code>for(auto itr : mp)&nbsp;</code></span></p>
<p><span style="font-size: 18px">等。下面看下 C++17 的改进:</span></p>
<pre class="language-cpp highlighter-hljs"><code>#include &lt;iostream&gt;
#include &lt;map&gt;

int main()
{
    std::map&lt;int, int&gt; map; // = { {1,4} };
    auto&amp;&amp; = map.insert({ 1, 1 });
    if (inserted)
      std::cout &lt;&lt; "inserted" &lt;&lt; std::endl;

    for (auto&amp;&amp; : map)
      std::cout &lt;&lt; "{" &lt;&lt; k &lt;&lt; ", " &lt;&lt; v &lt;&lt; "}" &lt;&lt; std::endl;
}</code></pre>
<p><span style="font-size: 18px">输出不变。相比 C++17,这里连 inserted 变量也不需要定义了,通过结构化绑定,直接原地定义返回的两个分量 (itr &amp; inserted);另外在遍历 map 元素时,也通过结构化绑定直接获取 first &amp; second (k &amp; v)</span><span style="font-size: 18px">,代码更简洁了。但对于一个不怎么关注新标准的老鸟,这是不是就有阅读障碍了?加之这种语言层面的变动多而细碎,如果打算先了解语法再深入协程,就很容易导致从入门到放弃的学习过程。</span></p>
<p><span style="font-size: 18px">为了将这个先有鸡先有蛋的乱麻问题破解掉,本文遵循以下原则:</span></p>
<p><span style="font-size: 18px">* 以协程为目标,涉及到的新语法会简单说明,不涉及的不旁征博引</span></p>
<p><span style="font-size: 18px">* 若语法的原理非常简单,也会简单展开讲讲,有利于了解其本质</span></p>
<p><span style="font-size: 18px">另外选取合适的 demo 也非常重要,太复杂的一下讲不清容易有挫折感,太简单的看了不知道有何用处也是一头雾水,本文选取的 demo 将在贴合实际的基础上尽量简化,以突出问题核心。</span></p>
<p><span style="font-size: 18px">最后说说工具的问题,自己搭建环境费时费力,现成的则不一定有合适的编译器版本,这里推荐两个工具:</span></p>
<p><span style="font-size: 18px">* Compile Explorer:在线编译 C++ 代码工具,查看汇编结果与运行结果,可切换编译器及版本、增加编译选项</span></p>
<p><span style="font-size: 18px"><img src="https://img2024.cnblogs.com/blog/1707550/202505/1707550-20250523174927977-664765178.png"></span></p>
<p><span style="font-size: 18px">* C++ Insights:也是编译工具,但不是生成汇编代码而是 C++ 表达的中间代码,可以用来查看 C++ 编译器底层做的一些工作,对于本文的主题 C++20 协程至关重要</span></p>
<p><img src="https://img2024.cnblogs.com/blog/1707550/202505/1707550-20250523175537367-1334404223.png"></p>
<p><span style="font-size: 18px">其实好多语法糖丢这里可以一眼露馅,比如上面的结构化绑定,其实在底层用的还是 std::pair,只不过编译器帮你省略了繁锁的细节,这比看反汇编是直观多了。</span></p>
<h1>协程本质</h1>
<p><span style="font-size: 18px">在进入 C++20 协程之前,有必要搞懂协程本身是什么,它能让出控制权、能继续执行、没有线程栈的切换</span><span style="font-size: 18px">,看起来似乎很神奇</span><span style="font-size: 18px">,一般函数可没有这个能力。</span></p>
<p><span style="font-size: 18px"><img src="https://img2024.cnblogs.com/blog/1707550/202506/1707550-20250606144313845-1116251558.png"></span></p>
<p><span style="font-size: 18px">早年间 C++17 的协程就是通过 duff device (switch case) 实现的:</span></p>
<pre class="language-cpp highlighter-hljs"><code>void fn(){
        int a, b, c;
        a = b + c;
        yield();
        b = c + a;
        yield();
        c = a + b;
}</code></pre>
<p><span style="font-size: 18px">其中 yield 就是协程让出控制权的点位,转换后变为这样:</span></p>
<pre class="language-cpp highlighter-hljs"><code>Struct fn{
    int a, b, c;
    int __state = 0;
   
    void resume(){
      switch(__state) {
      case 0:
             return fn1();
      case 1:
             return fn2();
      case 2:
             return fn3();
      }
    }
   
    void fn1(){
      a = b + c;
      __state ++;
    }
   
    void fn2(){
      b = c + a;
      __state ++;
    }
   
    void fn3(){
      c = a + b;
      __state ++;
    }
};</code></pre>
<p><span style="font-size: 18px">所以 yield 其实就是函数 return,而协程本质就是<strong>函数+状态机</strong>,这个之前文章里都已经说过了,那 C++20 协程有本质不同吗?答案是没区别。下面来看一个典型的 C++20 协程例子,并根据编译器中间结果来印证上面的结论。</span></p>
<pre class="language-cpp highlighter-hljs"><code>#include &lt;coroutine&gt;
#include &lt;iostream&gt;

struct Generator {
    struct promise_type {
      int current_value;
      auto get_return_object() { return Generator{this}; }
      auto initial_suspend() { return std::suspend_always{}; }
      auto final_suspend() noexcept { return std::suspend_always{}; }
      void unhandled_exception() {}
      auto yield_value(int value) {
            current_value = value;
            return std::suspend_always{};
      }
    };

    std::coroutine_handle&lt;promise_type&gt; handle;
    Generator(promise_type* p) : handle(std::coroutine_handle&lt;promise_type&gt;::from_promise(*p)) {}
    ~Generator() { if (handle) handle.destroy(); }
    bool next() { return !handle.done() &amp;&amp; (handle.resume(), !handle.done()); }
    int value() { return handle.promise().current_value; }
};

Generator range(int from, int to) {
    for (int i = from; i &lt;= to; ++i) {
      co_yield i;
    }
}

int main() {
    auto gen = range(1, 5);
    while (gen.next()) {
      std::cout &lt;&lt; gen.value() &lt;&lt; std::endl;
    }
}</code></pre>
<p><span style="font-size: 18px">这个例子演示了一个数列生成器,运行有如下输出:</span></p>
<pre class="language-bash highlighter-hljs"><code>1
2
3
4
5</code></pre>
<p><span style="font-size: 18px">其中<strong>协程体</strong> range 十分短小精悍:</span></p>
<pre class="language-cpp highlighter-hljs"><code>Generator range(int from, int to) {
    for (int i = from; i &lt;= to; ++i) {
      co_yield i;
    }
}</code></pre>
<p><span style="font-size: 18px">通过 co_yeild 不停的返回数列值。协程的返回类型<code>Generator</code> 是关键,称作<strong>返回对象</strong>,它要实现一系列接口,可以看做是 C++20 协程与用户的一个约定,这点就如同任意一个 C++ 类,实现了 <code>operator()</code> 接口就能被当作函数对象一样。凡是写 C++20 协程,必离不开返回对象,它内部又有两个约定:</span></p>
<p><span style="font-size: 18px">* <code>struct promise_type</code>,<strong>承诺对象</strong>。定义于返回对象内部的 traits 类型,用于定制协程行为,由用户实现,会被协程体访问</span></p>
<p><span style="font-size: 18px">* <code>std::coroutine_handle&lt;promise_type&gt; handle</code>,<strong>协程句柄</strong>。用于控制协程体的运行,由编译器实现,用户访问</span></p>
<p><span style="font-size: 18px">这里暂不展开解释 <code>Generator</code> 的各个成员功用,反正就把它当成一个模板,写协程抄上就完事儿。</span></p>
<p><span style="font-size: 18px">先了解下 main 是如何运转起来的,主要关注</span><span style="font-size: 18px"><code>Generator::next</code> 方法</span>:</p>
<pre class="language-cpp highlighter-hljs"><code>int main() {
    auto gen = range(1, 5);
    while (gen.next()) {
      std::cout &lt;&lt; gen.value() &lt;&lt; std::endl;
    }
}</code></pre>
<p><span style="font-size: 18px">它通过协程句柄的<code>resume</code>&amp;<code>done</code>来驱动协程运转:</span></p>
<pre class="language-cpp highlighter-hljs"><code>    bool next() { return !handle.done() &amp;&amp; (handle.resume(), !handle.done()); }</code></pre>
<p><span style="font-size: 18px">main 其实就是 next 的循环,直到协程彻底完结,因此 demo </span><span style="font-size: 18px">实际上演示了协程的 5 次进入和 5 次离开。</span></p>
<p><span style="font-size: 18px">demo 底层是如何实现的?循环变量是如何恢复的?带着这些疑问,</span><span style="font-size: 18px">有请 C++ Insights 上场,看看这个 demo 的原形 (注意开启 <code>Show coroutine transformation</code> 选项):</span></p>
<details>
<summary>查看代码</summary>
<pre class="language-cpp highlighter-hljs"><code>&nbsp;/*************************************************************************************
* NOTE: The coroutine transformation you've enabled is a hand coded transformation! *
*       Most of it is _not_ present in the AST. What you see is an approximation.   *
*************************************************************************************/
#include &lt;coroutine&gt;
#include &lt;iostream&gt;

struct Generator
{
struct promise_type
{
    int current_value;
    inline Generator get_return_object()
    {
      return Generator{this};
    }
   
    inline std::suspend_always initial_suspend()
    {
      return std::suspend_always{};
    }
   
    inline std::suspend_always final_suspend() noexcept
    {
      return std::suspend_always{};
    }
   
    inline void unhandled_exception()
    {
    }
   
    inline std::suspend_always yield_value(int value)
    {
      this-&gt;current_value = value;
      return std::suspend_always{};
    }
   
    // inline constexpr promise_type() noexcept = default;
};

std::coroutine_handle&lt;promise_type&gt; handle;
inline Generator(promise_type * p)
: handle{std::coroutine_handle&lt;promise_type&gt;::from_promise(*p)}
{
}

inline ~Generator() noexcept
{
    if(this-&gt;handle.operator bool()) {
      this-&gt;handle.destroy();
    }
   
}

inline bool next()
{
    return !this-&gt;handle.done() &amp;&amp; (this-&gt;handle.resume() , !this-&gt;handle.done());
}

inline int value()
{
    return this-&gt;handle.promise().current_value;
}

};


struct __rangeFrame
{
void (*resume_fn)(__rangeFrame *);
void (*destroy_fn)(__rangeFrame *);
std::__coroutine_traits_impl&lt;Generator&gt;::promise_type __promise;
int __suspend_index;
bool __initial_await_suspend_called;
int from;
int to;
int i;
std::suspend_always __suspend_24_11;
std::suspend_always __suspend_26_9;
std::suspend_always __suspend_24_11_1;
};

Generator range(int from, int to)
{
/* Allocate the frame including the promise */
/* Note: The actual parameter new is __builtin_coro_size */
__rangeFrame * __f = reinterpret_cast&lt;__rangeFrame *&gt;(operator new(sizeof(__rangeFrame)));
__f-&gt;__suspend_index = 0;
__f-&gt;__initial_await_suspend_called = false;
__f-&gt;from = std::forward&lt;int&gt;(from);
__f-&gt;to = std::forward&lt;int&gt;(to);

/* Construct the promise. */
new (&amp;__f-&gt;__promise)std::__coroutine_traits_impl&lt;Generator&gt;::promise_type{};

/* Forward declare the resume and destroy function. */
void __rangeResume(__rangeFrame * __f);
void __rangeDestroy(__rangeFrame * __f);

/* Assign the resume and destroy function pointers. */
__f-&gt;resume_fn = &amp;__rangeResume;
__f-&gt;destroy_fn = &amp;__rangeDestroy;

/* Call the made up function with the coroutine body for initial suspend.
   This function will be called subsequently by coroutine_handle&lt;&gt;::resume()
   which calls __builtin_coro_resume(__handle_) */
__rangeResume(__f);


return __f-&gt;__promise.get_return_object();
}

/* This function invoked by coroutine_handle&lt;&gt;::resume() */
void __rangeResume(__rangeFrame * __f)
{
try
{
    /* Create a switch to get to the correct resume point */
    switch(__f-&gt;__suspend_index) {
      case 0: break;
      case 1: goto __resume_range_1;
      case 2: goto __resume_range_2;
      case 3: goto __resume_range_3;
    }
   
    /* co_await insights.cpp:24 */
    __f-&gt;__suspend_24_11 = __f-&gt;__promise.initial_suspend();
    if(!__f-&gt;__suspend_24_11.await_ready()) {
      __f-&gt;__suspend_24_11.await_suspend(std::coroutine_handle&lt;Generator::promise_type&gt;::from_address(static_cast&lt;void *&gt;(__f)).operator std::coroutine_handle&lt;void&gt;());
      __f-&gt;__suspend_index = 1;
      __f-&gt;__initial_await_suspend_called = true;
      return;
    }
   
    __resume_range_1:
    __f-&gt;__suspend_24_11.await_resume();
    for(__f-&gt;i = __f-&gt;from; __f-&gt;i &lt;= __f-&gt;to; ++__f-&gt;i) {
      
      /* co_yield insights.cpp:26 */
      __f-&gt;__suspend_26_9 = __f-&gt;__promise.yield_value(__f-&gt;i);
      if(!__f-&gt;__suspend_26_9.await_ready()) {
      __f-&gt;__suspend_26_9.await_suspend(std::coroutine_handle&lt;Generator::promise_type&gt;::from_address(static_cast&lt;void *&gt;(__f)).operator std::coroutine_handle&lt;void&gt;());
      __f-&gt;__suspend_index = 2;
      return;
      }
      
      __resume_range_2:
      __f-&gt;__suspend_26_9.await_resume();
    }
   
    goto __final_suspend;
} catch(...) {
    if(!__f-&gt;__initial_await_suspend_called) {
      throw ;
    }
   
    __f-&gt;__promise.unhandled_exception();
}

__final_suspend:

/* co_await insights.cpp:24 */
__f-&gt;__suspend_24_11_1 = __f-&gt;__promise.final_suspend();
if(!__f-&gt;__suspend_24_11_1.await_ready()) {
    __f-&gt;__suspend_24_11_1.await_suspend(std::coroutine_handle&lt;Generator::promise_type&gt;::from_address(static_cast&lt;void *&gt;(__f)).operator std::coroutine_handle&lt;void&gt;());
    __f-&gt;__suspend_index = 3;
    return;
}

__resume_range_3:
__f-&gt;destroy_fn(__f);
}

/* This function invoked by coroutine_handle&lt;&gt;::destroy() */
void __rangeDestroy(__rangeFrame * __f)
{
/* destroy all variables with dtors */
__f-&gt;~__rangeFrame();
/* Deallocating the coroutine frame */
/* Note: The actual argument to delete is __builtin_coro_frame with the promise as parameter */
operator delete(static_cast&lt;void *&gt;(__f), sizeof(__rangeFrame));
}


int main()
{
Generator gen = range(1, 5);
while(gen.next()) {
    std::cout.operator&lt;&lt;(gen.value()).operator&lt;&lt;(std::endl);
}

return 0;
}</code></pre>
</details>
<p><span style="font-size: 18px">内容比较长,从头到尾分块解析一下。</span></p>
<p><span style="font-size: 18px"><img src="https://img2024.cnblogs.com/blog/1707550/202506/1707550-20250606154808438-274343796.png"></span></p>
<p><span style="font-size: 18px">承诺对象部分,一对一,比较直观,不多做解释了。</span></p>
<p><span style="font-size: 18px"><img src="https://img2024.cnblogs.com/blog/1707550/202506/1707550-20250606155025905-648817106.png"></span></p>
<p><span style="font-size: 18px">返回对象部分,也是如此,其中包含了协程句柄和一些自定义的接口。</span></p>
<p><span style="font-size: 18px">注意返回对象的构造函数,接收一个承诺对象指针,并将其设置到协程句柄,该参数将由编译器构造并传入。</span></p>
<pre class="language-cpp highlighter-hljs"><code>struct __rangeFrame
{
void (*resume_fn)(__rangeFrame *);
void (*destroy_fn)(__rangeFrame *);
std::__coroutine_traits_impl&lt;Generator&gt;::promise_type __promise;
int __suspend_index;
bool __initial_await_suspend_called;
int from;
int to;
int i;
std::suspend_always __suspend_24_11;
std::suspend_always __suspend_26_9;
std::suspend_always __suspend_24_11_1;
};</code></pre>
<p><span style="font-size: 18px"><code>__rangeFrame</code>称为<strong>协程状态,</strong>是由编译器生成的</span><span style="font-size: 18px">,没有对应的用户代码</span><span style="font-size: 18px">,用于保存协程体相关的必要信息:</span></p>
<p><span style="font-size: 18px">* <code>resume_fn</code>&amp;<code>destroy_fn</code>:<code>resume</code>&amp;<code>destroy</code> 回调,用于继续执行或销毁协程体</span></p>
<p><span style="font-size: 18px">* <code>__promise</code>:用户提供的承诺对象,用于定制协程行为</span></p>
<p><span style="font-size: 18px">* <code>__suspend_index</code>:duff device 状态机的状态值</span></p>
<p><span style="font-size: 18px">* <code>__initial_await_suspend_called</code>:不重要忽略</span></p>
<p><span style="font-size: 18px">* <code>from</code>&amp;<code>to</code>&amp;<code>i</code>:协程体参数 &amp; 栈变量</span></p>
<p><span style="font-size: 18px">* <code>__suspend_24_11</code>&amp;<code>__suspend_26_9</code>&amp;<code>__suspend_24_11_1</code>:协程挂起点的等待对象</span></p>
<p><span style="font-size: 18px">下面看看它是如何初始化的:</span></p>
<pre class="language-cpp highlighter-hljs"><code>Generator range(int from, int to)
{
/* Allocate the frame including the promise */
/* Note: The actual parameter new is __builtin_coro_size */</code></pre>
<p>构建协程状态</p>
<pre class="language-cpp highlighter-hljs"><code>__rangeFrame * __f = reinterpret_cast&lt;__rangeFrame *&gt;(operator new(sizeof(__rangeFrame)));</code></pre>
<p>初始化状态机</p>
<pre class="language-cpp highlighter-hljs"><code>__f-&gt;__suspend_index = 0;
__f-&gt;__initial_await_suspend_called = false;</code></pre>
<p>初始化协程参数</p>
<pre class="language-cpp highlighter-hljs"><code>__f-&gt;from = std::forward&lt;int&gt;(from);
__f-&gt;to = std::forward&lt;int&gt;(to);</code></pre>
<p>原地构建承诺对象 (只调构建函数不分配内存)</p>
<pre class="language-cpp highlighter-hljs"><code>/* Construct the promise. */
new (&amp;__f-&gt;__promise)std::__coroutine_traits_impl&lt;Generator&gt;::promise_type{};</code></pre>
<p>初始化协程控制接口</p>
<pre class="language-cpp highlighter-hljs"><code>/* Forward declare the resume and destroy function. */
void __rangeResume(__rangeFrame * __f);
void __rangeDestroy(__rangeFrame * __f);

/* Assign the resume and destroy function pointers. */
__f-&gt;resume_fn = &amp;__rangeResume;
__f-&gt;destroy_fn = &amp;__rangeDestroy;</code></pre>
<p>协程的第一次进入,就交给 resume 吧</p>
<pre class="language-cpp highlighter-hljs"><code>/* Call the made up function with the coroutine body for initial suspend.
   This function will be called subsequently by coroutine_handle&lt;&gt;::resume()
   which calls __builtin_coro_resume(__handle_) */
__rangeResume(__f);</code></pre>
<p>协程的第一次离开,需要 return 返回对象</p>
<pre class="language-cpp highlighter-hljs"><code>return __f-&gt;__promise.get_return_object();
}</code></pre>
<p><span style="font-size: 18px">注意原协程函数已经被掏空,实际上放置的是协程状态等各种对象的初始化代码,协程的第一次进入是通过直接调用协程体的 <code>resume</code> 完成的,因此重点其实转移到了 <code>__rangeResume</code> 这个编译器生成的方法中,这个稍后详述。接着往下就是离开协程的 return 语句,它会通过<code>promise_type::get_return_object</code>来返回返回对象:</span></p>
<pre class="language-cpp highlighter-hljs"><code>      auto get_return_object() { return Generator{this}; }</code></pre>
<p><span style="font-size: 18px">这里用到了聚合初始化,将 this 代表的承诺对象传递给了返回对象</span><span style="font-size: 18px">:</span></p>
<pre class="language-cpp highlighter-hljs"><code>    Generator(promise_type* p) : handle(std::coroutine_handle&lt;promise_type&gt;::from_promise(*p)) {}</code></pre>
<p><span style="font-size: 18px">返回对象会将承诺对象保存在协程句柄中以待后用,这个代码之前贴过就不赘述了。</span></p>
<p><img src="https://img2024.cnblogs.com/blog/1707550/202506/1707550-20250616112538419-596397409.png" alt="" height="461" width="625"></p>
<p><span style="font-size: 18px">上面这张图展示了协程体、协程状态、承诺对象、返回对象、协程句柄之间的关系,</span><span style="font-size: 18px">这里补充一下协程句柄<code>coroutine_handle</code>的代码:</span></p>
<pre class="language-cpp highlighter-hljs"><code>template&lt;&gt; struct coroutine_handle&lt;void&gt; {
    void*    m_ptr;
    bool done() { return __builtin_coro_done(m_ptr); }
    void resume() { __builtin_coro_resume(m_ptr); }
    void operator()() { resume(); }
    void destroy() { __builtin_coro_destroy(m_ptr); }
    operator bool() { return bool(m_ptr); }
    void* address() { return m_ptr; }
    static coroutine_handle from_address(void*);
    coroutine_handle();
    coroutine_handle(std::nullptr_t);
    coroutine_handle&amp; operator=(std::nullptr_t);
};</code></pre>
<p><span style="font-size: 18px">代码做了简化。与想象的不同,这里没有直接调用协程状态的 <code>resume_fn</code> 和 <code>destroy_fn</code>,实际上也无法调用它们,毕竟协程句柄只有一个承诺对象还拿不到协程状态。</span></p>
<p><span style="font-size: 18px">不过这也不是什么不可逾越的难事,基于结构体成员<code>__promise</code>的相对位置做计算是行得通的,想想看<code>offsetof</code>这种设施就明白了,只是</span><span style="font-size: 18px">就有点依赖编译器生成的结构体成员尺寸和顺序了。为了屏蔽这些细节,clang 中<code>done</code></span><span style="font-size: 18px"><code>resume</code></span><span style="font-size: 18px"><code>destroy</code>是委托给 <code>__builtin_coro_done</code></span><span style="font-size: 18px"><code>__builtin_coro_resume</code></span><span style="font-size: 18px"><code>__builtin_coro_destroy</code>这些内置函数的,MSVC 中是委托给<code>_coro_done</code><code>_coro_resume</code><code>_coro_destroy</code>,看得出来,这些实现是编译器相关的,不具备可移植性。</span></p>
<p><span style="font-size: 18px">网上搜了一下,没有找到相关源码来一窥究竟,不过开个脑洞想象一下:<code>resume</code>和<code>destroy</code>比较简单,一对一调用<code>__rangeResume</code>和<code>__rangeDestroy</code>就好了;<code>done</code>难搞一些,可能需要判断状态机当前值<code>__suspend_index</code>,贴个协程状态的定义回忆一下:</span></p>
<pre class="language-cpp highlighter-hljs"><code>struct __rangeFrame
{
void (*resume_fn)(__rangeFrame *);
void (*destroy_fn)(__rangeFrame *);
std::__coroutine_traits_impl&lt;Generator&gt;::promise_type __promise;
int __suspend_index;
bool __initial_await_suspend_called;
int from;
int to;
int i;
std::suspend_always __suspend_24_11;
std::suspend_always __suspend_26_9;
std::suspend_always __suspend_24_11_1;
};</code></pre>
<p><span style="font-size: 18px"><code>__suspend_index</code> 被初始化为0,每切换一次状态递增 1,所以只要这个值达到上限,就能说明整个协程结束了。</span></p>
<p><span style="font-size: 18px">下面来看协程的核心<code>__rangeResume</code>是否和预测的一样:</span></p>
<pre class="language-cpp highlighter-hljs"><code>/* This function invoked by coroutine_handle&lt;&gt;::resume() */
void __rangeResume(__rangeFrame * __f)
{</code></pre>
<p>开幕雷击,这不就是个 duff device 吗,与 C++17 不同的是这里使用了 goto 分散代码</p>
<pre class="language-cpp highlighter-hljs"><code>try
{
    /* Create a switch to get to the correct resume point */
    switch(__f-&gt;__suspend_index) {
      case 0: break;
      case 1: goto __resume_range_1;
      case 2: goto __resume_range_2;
      case 3: goto __resume_range_3;
    }</code></pre>
<p>启动时挂起?是的话会发生第一次退出,本例中 promise_type::inistialze_suspend 返回的 suspend_always 会导致协程挂起,这个发生在 range 内部直调 __rangeResume 时</p>
<pre class="language-cpp highlighter-hljs"><code>    /* co_await insights.cpp:24 */
    __f-&gt;__suspend_24_11 = __f-&gt;__promise.initial_suspend();
    if(!__f-&gt;__suspend_24_11.await_ready()) {
      __f-&gt;__suspend_24_11.await_suspend(std::coroutine_handle&lt;Generator::promise_type&gt;::from_address(static_cast&lt;void *&gt;(__f)).operator std::coroutine_handle&lt;void&gt;());
      __f-&gt;__suspend_index = 1;
      __f-&gt;__initial_await_suspend_called = true;
      return;
    } </code></pre>
<p>__resume_range_1 标签,这个发生在 while 循环中的 Generator::next 恢复协程运行时,注意循环中使用的变量已经放在了协程状态 _f 中,因此循环是无缝恢复的</p>
<pre class="language-cpp highlighter-hljs"><code>    __resume_range_1:
    __f-&gt;__suspend_24_11.await_resume();
    for(__f-&gt;i = __f-&gt;from; __f-&gt;i &lt;= __f-&gt;to; ++__f-&gt;i) {</code></pre>
<p>co_yield 将调用 promise_type::yield_value 将生成结果保存在返回对象中,同时 yield_value 返回的 suspend_always 会导致协程挂起,从而让外部访问 value 内容。注意这个阶段 __suspend_index 一直保持 2 不变</p>
<pre class="language-cpp highlighter-hljs"><code>      /* co_yield insights.cpp:26 */
      __f-&gt;__suspend_26_9 = __f-&gt;__promise.yield_value(__f-&gt;i);
      if(!__f-&gt;__suspend_26_9.await_ready()) {
      __f-&gt;__suspend_26_9.await_suspend(std::coroutine_handle&lt;Generator::promise_type&gt;::from_address(static_cast&lt;void *&gt;(__f)).operator std::coroutine_handle&lt;void&gt;());
      __f-&gt;__suspend_index = 2;
      return;
      } </code></pre>
<p>__resume_range_2 标签,这个也发生在 while 循环中的 Generator::next 恢复协程运行时</p>
<pre class="language-cpp highlighter-hljs"><code>      __resume_range_2:
      __f-&gt;__suspend_26_9.await_resume();
    }
   
    goto __final_suspend;</code></pre>
<p>整个 for 循环期间抛出的任何异常都会被编译器捕获,并回调 promise_type::unhandled_exception 处理</p>
<pre class="language-cpp highlighter-hljs"><code>} catch(...) {
    if(!__f-&gt;__initial_await_suspend_called) {
      throw ;
    }
   
    __f-&gt;__promise.unhandled_exception();
}</code></pre>
<p>结束时挂起?是的话会发生最后一次退出,本例中 promise_type::final_suspend 给的 suspend_always 会导致协程挂起</p>
<pre class="language-cpp highlighter-hljs"><code>__final_suspend:

/* co_await insights.cpp:24 */
__f-&gt;__suspend_24_11_1 = __f-&gt;__promise.final_suspend();
if(!__f-&gt;__suspend_24_11_1.await_ready()) {
    __f-&gt;__suspend_24_11_1.await_suspend(std::coroutine_handle&lt;Generator::promise_type&gt;::from_address(static_cast&lt;void *&gt;(__f)).operator std::coroutine_handle&lt;void&gt;());
    __f-&gt;__suspend_index = 3;
    return;
} </code></pre>
<p>__resume_range_3 标签,理论上没机会执行。若走到这里,会调用 __rangeDestroy 做一些清理工作</p>
<pre class="language-cpp highlighter-hljs"><code>__resume_range_3:
__f-&gt;destroy_fn(__f);
}</code></pre>
<p><span style="font-size: 18px">先补充承诺对象的两个接口定义:</span></p>
<pre class="language-cpp highlighter-hljs"><code>      auto initial_suspend() { return std::suspend_always{}; }
      auto final_suspend() noexcept { return std::suspend_always{}; }</code></pre>
<p><span style="font-size: 18px">它们分别用于控制协程开始、结束前是否挂起,返回的类型称为<strong>等待对象</strong>,主要由三个接口组成:</span></p>
<pre class="language-cpp highlighter-hljs"><code>struct suspend_always   // 永远挂起协程
{
constexpr bool await_ready() const noexcept { return false; }
constexpr void await_suspend(coroutine_handle&lt;&gt;) const noexcept {}
constexpr void await_resume() const noexcept {}
};

struct suspend_never    // 永远继续协程
{
constexpr bool await_ready() const noexcept { return true; }
constexpr void await_suspend(coroutine_handle&lt;&gt;) const noexcept {}
constexpr void await_resume() const noexcept {}
};</code></pre>
<p><span style="font-size: 18px"><code>await_suspend</code> &amp; <code>await_resume</code> 用于穿插一些协程挂起前、恢复后的工作,一般配合 <code>co_await</code>、<code>co_yeild</code>食用;上面列的这两个结构是特殊的等待对象,</span><span style="font-size: 18px">它们只实现了一个接口</span><span style="font-size: 18px"><code>await_ready</code>,用于指示是否挂起协程:<code>std::suspend_always</code>总是挂起,<code>std::suspend_never</code>总是不挂起;</span><span style="font-size: 18px">有了等待对象的铺垫,再来看看通读 <code>__rangeResume</code>的收获:</span></p>
<p><span style="font-size: 18px">1) C++20 协程本质仍是个 duff device</span></p>
<p><span style="font-size: 18px">2) 承诺对象的 <code>initial_suspend</code> 决定是否在初始化后挂起协程</span></p>
<p><span style="font-size: 18px">3) 承诺对象的 <code>final_suspend</code> 决定是否在结束前挂起协程</span></p>
<p><span style="font-size: 18px">4) 等待对象的 <code>await_ready</code> 表示异步结果是否就绪,若未就绪,需要挂起协程进行等待;否则继续协程</span></p>
<p><span style="font-size: 18px">5) <code>co_yield</code> 对应承诺对象的 <code>yield_value</code> 接口,接口参数表示当前需要保存的数据</span></p>
<p><span style="font-size: 18px">6) 协程挂起前会调用等待对象的 <code>await_suspend</code>,一般在这里发起异步调用</span></p>
<p><span style="font-size: 18px">7) 协程恢复后会调用等待对象的 <code>await_resume</code>,一般在这里获取异步调用结果</span></p>
<p><span style="font-size: 18px">8) 等待对象需要跨越协程的挂起状态进行访问,因此是放在协程状态中用于<strong>生命周期保持</strong>,这就是成员<code>__suspend_xx_xx</code>存在的底层逻辑</span></p>
<p><span style="font-size: 18px">9) 协程挂起前会更新 <code>__suspend_index</code>,以便下次进入时在新位置执行代码</span></p>
<p><span style="font-size: 18px">第 4) 点补充一个承诺对象的 <code>yield_value</code> 实现:</span></p>
<pre class="language-cpp highlighter-hljs"><code>      auto yield_value(int value) {
            current_value = value;
            return std::suspend_always{};
      }</code></pre>
<p><span style="font-size: 18px">生成值被保存到返回对象中,方便后续通过 value 接口访问:</span></p>
<pre class="language-cpp highlighter-hljs"><code>    int value() { return handle.promise().current_value; }</code></pre>
<p><span style="font-size: 18px">以上就是协程 <code>resume</code> 的逻辑了,下面来看下 <code>destroy</code>:</span></p>
<pre class="language-cpp highlighter-hljs"><code>/* This function invoked by coroutine_handle&lt;&gt;::destroy() */
void __rangeDestroy(__rangeFrame * __f)
{
/* destroy all variables with dtors */
__f-&gt;~__rangeFrame();
/* Deallocating the coroutine frame */
/* Note: The actual argument to delete is __builtin_coro_frame with the promise as parameter */
operator delete(static_cast&lt;void *&gt;(__f), sizeof(__rangeFrame));
}</code></pre>
<p><span style="font-size: 18px">用于销毁协程状态。有两个点会走到协程销毁,一个是上面展示过的 <code>__rangeResume</code>末尾,一个是返回对象的析构:</span></p>
<pre class="language-bash highlighter-hljs"><code>    ~Generator() { if (handle) handle.destroy(); }</code></pre>
<p><span style="font-size: 18px">那到底是走哪个销毁的呢?增加一些输出看看:</span></p>
<pre class="language-cpp highlighter-hljs"><code>class Generator {
    struct promise_type {
      ~promise_type() { std::cout &lt;&lt; "promise destroy" &lt;&lt; std::endl; }
      ...
    }
    ...
    ~Generator() { if (handle) { std::cout &lt;&lt; "before destroy handle" &lt;&lt; std::endl; handle.destroy(); std::cout &lt;&lt; "after destroy handle" &lt;&lt; std::endl; } }
}</code></pre>
<pre class="language-cpp highlighter-hljs"><code>1
2
3
4
5
before destroy handle
promise destroy
after destroy handle</code></pre>
<p><span style="font-size: 18px">承诺对象是协程状态的一个成员,它的析构意味着协程状态的销毁即<code>__rangeDestroy</code> 的调用,承诺对象析构的输出是包围在返回对象析构的输出中,这说明协程句柄的 <code>destroy</code> 直接调用了 <code>__rangeDestroy</code>,在 <code>__rangeResume</code> 末尾的自行销毁的代码<code>__resume_range_3</code>应该是没机会执行了,否则后面返回对象访问协程句柄时该崩溃了。</span></p>
<p><span style="font-size: 18px"><img src="https://img2024.cnblogs.com/blog/1707550/202506/1707550-20250616173429776-195820940.png"></span></p>
<p><span style="font-size: 18px">整个销毁顺序可以参考图上标的序号。</span></p>
<h1>后记</h1>
<p><span style="font-size: 18px">把这个 demo 生成数列的过程通过表格完整模拟一遍:</span></p>
<table style="border-collapse: collapse; width: 98.8469%; height: 162px" border="1">
<tbody>
<tr style="height: 21px">
<td style="width: 12.6998%; height: 21px">语句</td>
<td style="width: 9.31817%; height: 21px">__suspend_index</td>
<td style="width: 7.13892%; height: 21px">from</td>
<td style="width: 6.91348%; height: 21px">to</td>
<td style="width: 7.96553%; height: 21px">i</td>
<td style="width: 11.0465%; height: 21px">__suspend_24_11</td>
<td style="width: 10.2951%; height: 21px">__suspend_26_9</td>
<td style="width: 10.5205%; height: 21px">__suspend_24_11_1</td>
<td style="width: 24.0469%; height: 21px">说明</td>
</tr>
<tr style="height: 15px">
<td style="width: 12.6998%; height: 15px">auto gen = range(1,5)</td>
<td style="width: 9.31817%; height: 15px">0 -&gt; 1</td>
<td style="width: 7.13892%; height: 15px">1</td>
<td style="width: 6.91348%; height: 15px">5</td>
<td style="width: 7.96553%; height: 15px">-</td>
<td style="width: 11.0465%; height: 15px">suspend_always</td>
<td style="width: 10.2951%; height: 15px">-</td>
<td style="width: 10.5205%; height: 15px">-</td>
<td style="width: 24.0469%; height: 15px">__resume_range_1 前退出</td>
</tr>
<tr style="height: 21px">
<td style="width: 12.6998%; height: 21px">gen.next()</td>
<td style="width: 9.31817%; height: 21px">1 -&gt; 2</td>
<td style="width: 7.13892%; height: 21px">1</td>
<td style="width: 6.91348%; height: 21px">5</td>
<td style="width: 7.96553%; height: 21px">1</td>
<td style="width: 11.0465%; height: 21px">suspend_always</td>
<td style="width: 10.2951%; height: 21px">suspend_always</td>
<td style="width: 10.5205%; height: 21px">-</td>
<td style="width: 24.0469%; height: 21px">__resume_range_2 前退出</td>
</tr>
<tr style="height: 21px">
<td style="width: 12.6998%; height: 21px">gen.next()</td>
<td style="width: 9.31817%; height: 21px">2</td>
<td style="width: 7.13892%; height: 21px">1</td>
<td style="width: 6.91348%; height: 21px">5</td>
<td style="width: 7.96553%; height: 21px">2</td>
<td style="width: 11.0465%; height: 21px">suspend_always</td>
<td style="width: 10.2951%; height: 21px">suspend_always</td>
<td style="width: 10.5205%; height: 21px">-</td>
<td style="width: 24.0469%; height: 21px">&nbsp;同上</td>
</tr>
<tr style="height: 21px">
<td style="width: 12.6998%; height: 21px">gen.next()</td>
<td style="width: 9.31817%; height: 21px">2</td>
<td style="width: 7.13892%; height: 21px">1</td>
<td style="width: 6.91348%; height: 21px">5</td>
<td style="width: 7.96553%; height: 21px">3</td>
<td style="width: 11.0465%; height: 21px">suspend_always</td>
<td style="width: 10.2951%; height: 21px">suspend_always</td>
<td style="width: 10.5205%; height: 21px">-</td>
<td style="width: 24.0469%; height: 21px">&nbsp;同上</td>
</tr>
<tr style="height: 21px">
<td style="width: 12.6998%; height: 21px">gen.next()</td>
<td style="width: 9.31817%; height: 21px">2</td>
<td style="width: 7.13892%; height: 21px">1</td>
<td style="width: 6.91348%; height: 21px">5</td>
<td style="width: 7.96553%; height: 21px">4</td>
<td style="width: 11.0465%; height: 21px">suspend_always</td>
<td style="width: 10.2951%; height: 21px">suspend_always</td>
<td style="width: 10.5205%; height: 21px">-</td>
<td style="width: 24.0469%; height: 21px">&nbsp;同上</td>
</tr>
<tr style="height: 21px">
<td style="width: 12.6998%; height: 21px">gen.next()</td>
<td style="width: 9.31817%; height: 21px">2</td>
<td style="width: 7.13892%; height: 21px">1</td>
<td style="width: 6.91348%; height: 21px">5</td>
<td style="width: 7.96553%; height: 21px">5</td>
<td style="width: 11.0465%; height: 21px">suspend_always</td>
<td style="width: 10.2951%; height: 21px">suspend_always</td>
<td style="width: 10.5205%; height: 21px">-</td>
<td style="width: 24.0469%; height: 21px">&nbsp;同上</td>
</tr>
<tr>
<td style="width: 12.6998%">gen.next()</td>
<td style="width: 9.31817%">2 -&gt; 3</td>
<td style="width: 7.13892%">1</td>
<td style="width: 6.91348%">5</td>
<td style="width: 7.96553%"><span style="color: rgba(224, 62, 45, 1)">6</span></td>
<td style="width: 11.0465%">suspend_always</td>
<td style="width: 10.2951%">suspend_always</td>
<td style="width: 10.5205%">suspend_always</td>
<td style="width: 24.0469%">__resume_range_3 前退出,next 返回 false</td>
</tr>
<tr style="height: 21px">
<td style="width: 12.6998%; height: 21px">gen.~Generator</td>
<td style="width: 9.31817%; height: 21px">-</td>
<td style="width: 7.13892%; height: 21px">-</td>
<td style="width: 6.91348%; height: 21px">-</td>
<td style="width: 7.96553%; height: 21px">-</td>
<td style="width: 11.0465%; height: 21px">-</td>
<td style="width: 10.2951%; height: 21px">-</td>
<td style="width: 10.5205%; height: 21px">-</td>
<td style="width: 24.0469%; height: 21px">&nbsp;清理资源释放内存</td>
</tr>
</tbody>
</table>
<p><span style="font-size: 18px">while 循环共 5 次;实际调用 next 6 次,因为最后一次 next 时 done 返回了 false 导致 while 循环退出,没有打印 value 值;加上初始化进入 range 的一次,共 7 次,你看懂了吗。</span></p>
<p><span style="font-size: 18px">不过表内有一点标红数据与实际有出入,即最后一次 value 仍为 5 而不是 6,这是测试代码:</span></p>
<pre class="language-cpp highlighter-hljs"><code>#include &lt;coroutine&gt;
#include &lt;iostream&gt;

struct Generator {
    struct promise_type {
      int current_value;
      auto get_return_object() { return Generator{this}; }
      auto initial_suspend() { return std::suspend_always{}; }
      auto final_suspend() noexcept { return std::suspend_always{}; }
      void unhandled_exception() {}
      auto yield_value(int value) {
            current_value = value;
            return std::suspend_always{};
      }
    };

    int n = 0;
    std::coroutine_handle&lt;promise_type&gt; handle;
    Generator(promise_type* p) : handle(std::coroutine_handle&lt;promise_type&gt;::from_promise(*p)) {}
    ~Generator() { if (handle) handle.destroy(); }
    bool next() { return !handle.done() &amp;&amp; (handle.resume(), n++, !handle.done()); }
    int value() { return handle.promise().current_value; }
    int N() { return n; }
};

Generator range(int from, int to) {
    for (int i = from; i &lt;= to; ++i) {
      co_yield i;
    }
}

int main() {
    auto gen = range(1, 5);
    while (gen.next()) {
      std::cout &lt;&lt; gen.N() &lt;&lt; ": " &lt;&lt; gen.value() &lt;&lt; std::endl;
    }

    std::cout &lt;&lt; gen.N() &lt;&lt; ": " &lt;&lt; gen.value() &lt;&lt; std::endl;
}</code></pre>
<p><span style="font-size: 18px">和输出:</span></p>
<pre class="language-cpp highlighter-hljs"><code>1: 1
2: 2
3: 3
4: 4
5: 5
6: 5</code></pre>
<p><span style="font-size: 18px">看起来像是编译器优化但我没有证据,感兴趣的读者可以研究汇编代码告诉我为什么。</span></p>
<h1>总结</h1>
<p><span style="font-size: 18px">本文通过一个最基础的例子说明了 C++20 协程相关的概念:</span></p>
<p><span style="font-size: 18px">* 协程体</span></p>
<p><span style="font-size: 18px">* 协程状态</span></p>
<p><span style="font-size: 18px">* 承诺对象</span></p>
<p><span style="font-size: 18px">* 返回对象</span></p>
<p><span style="font-size: 18px">* 协程句柄</span></p>
<p><span style="font-size: 18px">和它们之间的关系:</span></p>
<p><span style="font-size: 18px"><img src="https://img2024.cnblogs.com/blog/1707550/202506/1707550-20250616112538419-596397409.png" alt="" height="461" width="625"></span></p>
<p><span style="font-size: 18px">另外通过两个在线工具,解析了编译器在底层所做的穿针引线工作,并阐释了 C++20 协程就是函数+状态机这一实质,并简单说明了接入 C++20 协程时用户需要实现的类型与接口,及其含义。</span></p>
<p><span style="font-size: 18px">用户只需要提供一个<strong>返回对象</strong>、一个<strong>承诺对象</strong>,若干<strong>等待对象</strong>,就可以像写函数一样写协程啦!相比 C++17 需要自己维护变量的生命期,C++20 直接将协程所需参数和变量搬到协程状态中,为用户节省了大量的心智负担,使协程达到真正好用的水平。这样做的好处是变量存放于堆上,可大可小,不受固定栈尺寸限制,避免爆栈问题侵扰;缺点是当协程数量多时,动态内存分配频繁,容易引发内存碎片。</span></p>
<p><span style="font-size: 18px">然而本例并不是一个能拿得出手的 C++20 协程例子,毕竟没有用户会手动 <code>resume</code> 协程!所以下一篇准备加入协程调度器,看看协程在真实场景中是怎么自己运转起来的,敬请期待~</span></p>
<h1>参考</h1>
<p><span style="font-size: 18px">.&nbsp;</span><span style="font-size: 18px">深入浅出 C++ Lambda表达式:语法、特点和应用</span></p>
<p><span style="font-size: 18px">.&nbsp;</span><span style="font-size: 18px">C++ Lambda表达式的完整介绍</span></p>
<p><span style="font-size: 18px">.&nbsp;</span><span role="heading" aria-level="2"><span style="font-size: 18px">C++11:lambda表达式</span></span></p>
<p><span style="font-size: 18px"><span role="heading" aria-level="2">.&nbsp;</span>C++11中的std::bind和std::function</span></p>
<p><span style="font-size: 18px">.&nbsp;现代C++的回调技术--std::bind+std::function</span></p>
<p><span style="font-size: 18px">.&nbsp;std::function的原理以及实现</span></p>
<p><span style="font-size: 18px">. <span class="el-breadcrumb__item" data-v-fc07bb3e="" aria-current="page"><span class="el-breadcrumb__inner" role="link"><span data-v-fc07bb3e="">C++代码优雅之道:std::function使用全攻略(附性能优化技巧)</span></span></span></span></p>
<p><span style="font-size: 18px"><span role="heading" aria-level="2">. </span>C++17结构化绑定</span></p>
<p><span style="font-size: 18px">. 【C++ 17 新特性 结构化绑定】深入理解C++ 17 结构化绑定 的处理</span></p>
<p><span style="font-size: 18px">. C++ 17 结构化绑定</span></p>
<p><span style="font-size: 18px">. </span><span style="vertical-align: middle" role="heading" aria-level="2"><span style="font-size: 18px">掌握 C++17:结构化绑定与拷贝消除的妙用</span></span></p>
<p><span style="font-size: 18px">.&nbsp;c++17之std::optional,std::variant以及std::any</span></p>
<p><span style="font-size: 18px">.&nbsp;如何优雅的使用 std::variant 与 std::optional</span></p>
<p><span style="font-size: 18px">.&nbsp;17. 使用std::optional和std::variant进行重构</span></p>
<p><span style="font-size: 18px">.&nbsp;理解C++折叠表达式(Fold Expression)</span></p>
<p><span style="font-size: 18px">. C++ 折叠表达式:优雅处理可变参数模板</span></p>
<p><span style="font-size: 18px">.&nbsp;【C++ 17 新特性 折叠表达式 fold expressions】理解学习 C++ 17 折叠表达式 的用法</span></p>
<p><span style="font-size: 18px">.&nbsp;C++中的聚合初始化</span></p>
<p><span style="font-size: 18px">.&nbsp;C++20 中使用括号进行聚合初始化:新特性与实践指南</span></p>
<p><span style="font-size: 18px">. 漫谈C++类型擦除(Type Erasure)</span></p>
<p><span style="font-size: 18px">. C++语法糖(coroutine)详解以及示例代码</span></p>
<p><span style="font-size: 18px">. </span><span role="heading" aria-level="2"><span style="font-size: 18px">【并发编程二十一:终章】c++20协程( co_yield、co_return、co_await )</span></span></p>
<p><span style="font-size: 18px"><span role="heading" aria-level="2">.&nbsp;</span>从无栈协程到C++异步框架</span></p>
<p><span style="font-size: 18px"><span role="heading" aria-level="2">.&nbsp;</span>从无栈协程到C++异步框架—多线程环境下的协程调度</span></p>
<p><span style="font-size: 18px">.&nbsp;</span><span role="heading" aria-level="2"><span style="font-size: 18px">gcc里的coroutine_handle</span></span></p>
<p><span style="font-size: 18px" role="heading" aria-level="2">.&nbsp;</span><span role="heading" aria-level="2"><span style="font-size: 18px">21. C++快速入门--协程 Coroutine 入门</span></span></p>
<p><span style="font-size: 18px"><span role="heading" aria-level="2">.</span> Exploring the C++ Coroutine</span></p>
<p><span style="font-size: 18px">.&nbsp;C++那些事之C++20协程</span></p>
<p><span style="font-size: 18px">.&nbsp;C++协程小记-2</span></p>
<p><span style="font-size: 18px">. 浅谈C++20 协程那点事儿</span></p>
<p><span style="font-size: 18px">.&nbsp;<span role="heading" aria-level="2">一篇文章搞懂 C++ 20 协程 Coroutine</span></span></p>
<p><span style="font-size: 18px"><span role="heading" aria-level="2">.&nbsp;</span>C++ 20 协程 Coroutine(3,剖析)</span></p>
<p><span style="font-size: 18px">.&nbsp;</span><span style="font-size: 18px">C++20 Coroutines: operator co_await</span></p>
<p><span style="font-size: 18px">.&nbsp;[翻译] 为什么 C++20 是最 awesome 的网络编程语言</span></p>
<p><span style="font-size: 18px">.&nbsp;从HelloWold开始,深入浅出C++ 20 Coroutine TS</span></p>
<p>&nbsp;</p>

</div>
<div id="MySignature" role="contentinfo">
    <p>本文来自博客园,作者:goodcitizen,转载请注明原文链接:https://www.cnblogs.com/goodcitizen/p/18889661/cpp20_coroutine_is_equal_to_function_plus_state_machine</p><br><br>
来源:https://www.cnblogs.com/goodcitizen/p/18889661/cpp20_coroutine_is_equal_to_function_plus_state_machine
頁: [1]
查看完整版本: 协程本质是函数加状态机——零基础深入浅出 C++20 协程