C++20协程示例

C++20协程示例

认识协程

在C++中,协程就是一个可以暂停和恢复的函数。

包含co_waitco_yieldco_return关键字的都可以叫协程。

看一个例子:

c++ 复制代码
MyCoroGenerator<int> testFunc(int n)
{
    std::cout << "Begin testFunc" << std::endl;
    for (int i = 0; i < n; ++i) {
        std::cout << "TestFunc before yield " << i << std::endl;
        co_yield i;
        std::cout << "TestFunc after yield " << i << std::endl;
    }
    std::cout << "End testFunc" << std::endl;
}

int main()
{
    int inp = 10;
    std::cout << "Before testFunc" << std::endl;
    MyCoroGenerator<int> gen = testFunc(inp);
    std::cout << "After testFunc" << std::endl;
    for (int i = 0; i < inp; ++i) {
        std::cout << "Cur input: " << i << std::endl;
        std::cout << "Output value: " << gen.next() << std::endl;
        std::cout << "After input: " << i << std::endl;
    }
}

上面这段代码的执行结果是:

txt 复制代码
Before testFunc
After testFunc
Cur input: 0
Output value: Begin testFunc
TestFunc before yield 0
0
After input: 0
Cur input: 1
Output value: TestFunc after yield 0
TestFunc before yield 1
1
After input: 1
Cur input: 2
Output value: TestFunc after yield 1
TestFunc before yield 2
2
After input: 2
Cur input: 3
Output value: TestFunc after yield 2
TestFunc before yield 3
3
After input: 3
Cur input: 4
Output value: TestFunc after yield 3
TestFunc before yield 4
4
After input: 4
Cur input: 5
Output value: TestFunc after yield 4
TestFunc before yield 5
5
After input: 5

...

调用时发现,函数并没有一开始就执行,而是等到next时才开始执行。执行到co_yield就会自动暂停,下次next才会继续执行

另外,函数本身并没有返回什么,但是这里可以使用MyCoroGenerator<int>接收。函数的控制权也转移到了MyCoroGenerator<int>,需要执行时就调用next

函数在暂停后执行,依然可以继续上次的状态,这是因为,协程在挂起期间,其上下文全部都被保留,下次执行时恢复。

协程句柄

std::coroutine_handle类模板是实现协程的最底层的工具,负责存储协程的句柄。他可以特化为std::coroutine_handle<Promise>或者std::coroutine_handle<void>

这里面的Promise是实现协程必要的Promise类,而且其名字必须是promise_type

协程会暂停,其上下文也会保留,std::coroutine_handle就是保存和管理协程的地方。

std::coroutine_handle中方法不多,但是每个都很重要,其中:

  • done方法,可以查询协程是否已经结束
  • resume,恢复一个协程的执行
  • destroy,销毁一个协程

std::coroutine_handle是一个很底层的东西,没有RAII包裹,就像一个裸指针那样,需要手动创建、手动销毁。

所以需要一个包裹类,负责处理协程句柄的初始化和销毁,也就是Generator

Generator

C++的协程要求Generator必须有promise_type这个类,名字也必须这个,不能是其他的名字,直接定义在Generator中最方便。

c++ 复制代码
template <typename T>
struct MyCoroGenerator {
    struct promise_type {
        // todo
    };
};

promise_type中有一些固定接口,这些接口如果不实现,那么协程是不完整的。

  • initial_suspend,协程是否在初始化结束后挂起,返回std::suspend_always、std::suspend_never,这是标准库里面已经定义好的类型,前者表示总是挂起,后者表示从不挂起
  • final_suspend,协程最后一次执行是否挂起,返回值和上面函数相同。由于final_suspend是收尾阶段的工作,所以必须是noexcept
  • unhandled_exception,处理协程中未被处理的异常
  • get_return_object,返回一个Generator对象
  • yield_value,处理协程返回值,就是co_yield传递过来的值
  • return_void,处理协程结束后的返回值,和return_value同时只能存在一个,如果没有返回值就用return_void
  • return_value,处理协程结束后返回值,和return_void同时只能存在一个,如果有返回值就用return_value

解释一下yield_value函数,co_yield实际是一个语法糖,相当于co_wait promise.yield_value(i),只有实现了该函数,Genreator才能接收co_yield的返回值

函数的返回值,需要回答协程要不要挂起,也就是std::suspend_always或者std::suspend_never,因为需要yield后挂起,所以返回std::suspend_always

为了接收返回值,需要用转发和std::optional,接收并存储返回值。

get_return_object负责创建一个Generator,一般来说,使用std::coroutine_handle<promise_type>::from_promise接口从一个Promise创建句柄,然后使用这个句柄创建Generator。

为了配合创建函数,MyGenerator需要实现一个接收句柄的构造函数。

参考:C++ coroutine generator 实现笔记

相关推荐
A懿轩A1 小时前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
机器视觉知识推荐、就业指导1 小时前
C++设计模式:享元模式 (附文字处理系统中的字符对象案例)
c++
半盏茶香1 小时前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
Ronin3052 小时前
11.vector的介绍及模拟实现
开发语言·c++
✿ ༺ ོIT技术༻2 小时前
C++11:新特性&右值引用&移动语义
linux·数据结构·c++
字节高级特工2 小时前
【C++】深入剖析默认成员函数3:拷贝构造函数
c语言·c++
唐诺8 小时前
几种广泛使用的 C++ 编译器
c++·编译器
冷眼看人间恩怨9 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
红龙创客9 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin9 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin