跳至正文
首页 » C++ 协程初见

C++ 协程初见

1. 什么是协程

cppreference中对协程做了如下定义如果一个函数拥有下面的特征,那么他就算是一个协程

  1. 使用co_await运算符挂起当前执行过程
  2. 使用co_yield运算符挂起当前执行过程并返回
  3. 使用co_return运算发完成当前执行过程并返回
  4. 协程不能有可变参数
  5. 协程不能使用return关键字返回,不能使用占位符返回类型包括autoconcept
  6. constexpr函数,构造析构函数不能是协程

2. 最简单的协程和它的’返回‘类型

常规函数通过返回一些结果包括返回空来向调用者返回。相比之下,协程不能使用return关键字。但是即使不使用return关键字协程的ReturnObject中也同样有一些设定好的返回对象,在ReturnObject对象中嵌套了promise_typepromise_type是一组特定的函数集合。

// 举个例子
#include <coroutine>  //  -fcoroutines

int foo() { co_return 2; }	// unable to find the promise type for this coroutine
int bar() { co_await 2; }	// unable to find the promise type for this coroutine
int foo2() { return 2; }	// ok
int bar2() { return 2; }	// ok

我举例了两个编译错误的协程函数和两个正常的普通函数

其中协程函数使用了co_returnco_awaitco_returnreturn关键字非常类似,co_return能被用来返回到调用者的上下文中,co_await则会返回到其他的协程上下文或者其调用者上下文中。有一点需要注意的是co_await是一个运算符,并需要一个有效的操作数,看起来像是co_await expr

可以看到编译错误提示unable to find the promise type for this coroutine,说明编译器无法找到promise type这个类型,在这种情况下,需要正确设置正确的promise type类型

根据以上信息,在cppreference中要求协程返回的对象必须嵌套promise_type,根据这个类名与实现方式编译器生成代码,该代码调用promise_type中的函数实现。

举个例子

struct ReturnObject {
	struct promise_type {                               // 嵌套类型
		promise_type() = default;                       // 默认构造函数
		ReturnObject get_return_object() { return {}; } // 在嵌套类型中返回外部类型
		Awaitable initial_suspend() { return {}; }      // 在协程创建时调用,并返回 Awaitable 类型
		Awaitable final_suspend() { return {}; }        // 在协程结束时调用,并返回 Awaitable 类型
		void unhandled_exception() {}                   // 用于处理一些意外异常
	}
}

ReturnObject bar() { co_await expr; }

// !不能运行,因为Awaitable未知
// Awaitable = std::suspend_always/std::suspend_never

在例子中Awaitable对象,实际为std::suspend_always或者std::suspend_never其中之一
使用std::suspend_always可在co_await时挂起协程
使用std::suspend_never可在co_await时不挂起协程

举个例子

struct ReturnObject{  
  struct promise_type{  
    promise_type() = default;  
    ReturnObject get_return_object() { return {}; }  
    std::suspend_always initial_suspend() { return {}; }
    std::suspend_never final_suspend(){ return {}; } 
    void unhandled_exception(){}  
  };  
};

ReturnObject foo(){  
    std::cout << "1. hello from coroutine";
    co_await std::suspend_always{};
    std::cout << "2. hello from coroutine";  
}

int main(){foo(); }

这个例子什么都不会输出,因为main()函数先构建了ReturnObject这个对象,顺序如下

  1. 使用promise_type构造函数
  2. 调用get_return_object()并保存结果于局部变量中
  3. 调用initial_suspend()并使用co_await运算此函数的返回值
    所以例子卡在了调用initial_suspend()上,如果将initial_suspend()的返回值改为std::suspend_never,则可以顺利执行第一条语句,如果将foo()中改为co_await std::suspend_never{};则可以一直执行。

最后一个例子可以看出执行顺序

#include <coroutine>
#include <iostream>

//global counter

int num{0};

struct ReturnObject{

	struct promise_type{
		promise_type(){std::cout << num++ << ". promise_type default ctor\n";}
		ReturnObject get_return_object() {
			std::cout << num++ << ". promise_type::get_return_object\n";
			return {};
		}
		std::suspend_never initial_suspend() {
			std::cout << num++ << ". promise_type::initial_suspend\n";
			return {};
		}

		std::suspend_never final_suspend(){
			std::cout << num++ << ". promise_type::final_suspend\n";
			return {};
		}

		void unhandled_exception(){std::cout << num++ << ".throw\n";}
	};
};

ReturnObject foo(){
	std::cout << num++ << ". hello from coroutine\n";
	co_await std::suspend_never{}; //never suspend the coroutine at this point
	//co_await std::suspend_always{}; //suspend the coroutine at this point
	std::cout << num++ << ". hello from coroutine\n";
}


int main(){foo();}

/* output
	0. promise_type default ctor
	1. promise_type::get_return_object
	2. promise_type::initial_suspend
	3.  hello from coroutine
	4.  hello from coroutine
	5. promise_type::final_suspend
*/
标签:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注