Última atualização:2026-10-12
Introdução às corrotinas no C++20¶
1.O que são Coroutines¶
A partir do C++20, o C++ finalmente oferece suporte a corrotinas; o C++23 deverá trazer suporte adicional, cobrindo pelo menos os casos de uso mais comuns.
Corrotina (em inglês: coroutine) é uma classe de componentes de programas de computador que generaliza as sub-rotinas do multitarefas cooperativo, permitindo que a execução seja suspensa e retomada. Em relação às sub-rotinas, as corrotinas são mais gerais e flexíveis, mas, na prática, não são usadas de forma tão ampla quanto as sub-rotinas. As corrotinas são mais adequadas para implementar componentes de programas familiares, como multitarefas cooperativo, tratamento de exceções, loop de eventos, iteradores, listas infinitas e pipes.
Um trecho da Wikipédia descreve a ideia básica das corrotinas.
Do ponto de vista do C++, pode-se entender de forma simples que uma corrotina é qualquer função que contenha co_return, co_yield ou co_await.
Atualmente, as corrotinas do C++20 são um açúcar sintático sobre objetos de função: o compilador gera uma estrutura de código em torno da corrotina. Esse código depende de promise types definidos pelo usuário. Isso pode mudar depois do C++23; até lá, seguimos as regras do C++20 e precisamos escrever esses tipos nós mesmos.
Mencionamos antes co_return, co_yield e co_await, que serão explicados em capítulos futuros.
2.Step By Step Coroutine¶
A seguir, começamos com um exemplo simples de coroutine; a corrotina abaixo não faz nada, e a chamaremos de HelloTask coroutine.
#include <coroutine>
struct HelloTask {
struct promise_type {
HelloTask get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
HelloTask myCoroutine() {
co_return; // make it a coroutine
}
int main() {
HelloTask x = myCoroutine();
}
Para compilar e executar esse código:
É necessário que o seu compilador ofereça suporte aos recursos do C++20.
Vamos percorrer de cima a abaixo esse programa HelloWorld e ver o que ele tem de diferente do desenvolvimento habitual.
2.1 Arquivo de cabeçalho¶
Primeiro, o arquivo de cabeçalho importa coroutine; aqui eu peguei diretamente o código-fonte do gcc-10 para ver o que exatamente ele faz.
https://github.com/gcc-mirror/gcc/blob/releases/gcc-10/libstdc++-v3/include/std/coroutine
O conteúdo inclui o seguinte:
// 用于发现协程promise_type的特征类型
// 核心代码:using promise_type = typename _Result::promise_type;
1.coroutine_traits
// 协程句柄,用于管理暂停或执行的协程以及Promise
2.coroutine_handle
// coroutine_handle全特化版本 是其他特化的公开基类。它保有handle的底层 地址。
2.1 coroutine_handle<void>
// coroutine_handle泛化版本
2.2 template <typename _Promise>
struct coroutine_handle : coroutine_handle<>
// coroutine_handle全特化版本 处理无操作协程
2.3 template <>
struct coroutine_handle<noop_coroutine_promise> : public coroutine_handle<>
3.平凡多可等待对象 Trivial awaitables
// await表达式应该暂停
3.1 suspend_always
// await表达式不应该暂停
3.2 suspend_never
Pode-se ver que ele contém muitos termos específicos:
- coroutine_handle
- Promise
- awaitable
entre outros.
Todo o conteúdo sobre corrotinas apresentado adiante gira em torno desses elementos.
2.2 Promise¶
Continuando com o HelloTask acima, percebemos que há um trecho como este:
struct HelloTask {
struct promise_type {
HelloTask get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
Então surge a pergunta: por que é preciso escrever promise_type aqui?
Escrevemos promise_type porque, como mencionado na seção 2.1 acima, falamos de handle; quando o compilador cria o handle da corrotina, é preciso especificar o promise_type do objeto dessa corrotina. Por isso, basicamente todos os objetos de corrotina têm uma estrutura HelloTask::promise_type. Quem for atento já entendeu: o promise_type é passado para lá, é resolvido dentro do Handle e, assim, determina-se o comportamento do objeto de corrotina personalizado pelo usuário.
Vamos olhar mais a fundo o que há dentro de promise_type: o conteúdo aqui é fixo; conforme o conteúdo posterior aumenta, as funções envolvidas também aumentam. Por exemplo, aqui usamos return_void; mais adiante, quando co_return retornar um objeto, será usada outra função.
get_return_object pode retornar o objeto personalizado atual do usuário; aqui ele não faz nada. Retomando o que vimos em 2.1, podemos usar coroutine_handle para isso:
struct promise_type {
using Handle = std::coroutine_handle<promise_type>;
HelloTask get_return_object() {
return HelloTask{Handle::from_promise(*this)};
}
// other member function
}
Vê-se que aqui o membro from_promise de coroutine_handle foi usado para construir um objeto.
initial_suspend: diz ao compilador se a corrotina deve permanecer suspensa na primeira execução, ou seja, se deve permanecer suspensa quando a corrotina é instanciada.
final_suspend: o oposto de initial_suspend; após a conclusão do corpo da função da corrotina, há um final_suspend no fim da corrotina. Isso dá a oportunidade de fazer limpezas adicionais.
Ambos retornam um awaitable; aqui usou-se o suspend_always do arquivo de cabeçalho, mas o usuário também pode definir seu próprio awaitable, o que será explicado em artigos posteriores.
O código-fonte de suspend_always e suspend_never é o seguinte:
struct suspend_always
{
constexpr bool await_ready() const noexcept { return false; }
constexpr void await_suspend(coroutine_handle<>) 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<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};
Isso inclui diversos membros:
- await_ready
- Se soubermos que o resultado já está pronto, podemos concluir de forma síncrona?
- await_suspend
- O valor de retorno pode ser bool, void ou coroutine handle.
- Aqui o retorno é void; nesse caso, o controle retorna imediatamente ao caller/resumer da Coroutine atual (esta Coroutine permanece suspensa); caso contrário, se await_suspend retornar bool, true significa entregar o controle ao caller/resumer, e false significa retomar a coroutine atual. O retorno de coroutine handle será abordado em artigos posteriores.
- await_resume
- O resultado dele é o resultado de toda a expressão co_await. Portanto, o valor de retorno não precisa ser void; pode ser um objeto personalizado, por exemplo.
return_void: quando usamos co_return;, return_void será chamado.
unhandled_exception: usado para capturar exceções.
Por fim, falta apenas o passo de criação da coroutine: conforme a definição do início, corrotina é qualquer função que contenha co_return, co_yield ou co_await. Daí surge:
2.3 Fluxo geral¶
Qual é o fluxo de chamadas como um todo? Nesta seção, detalharemos a lógica de execução do código acima. Antes de começar, alteramos o código adicionando algumas impressões:
#include <iostream>
#include <coroutine>
struct HelloTask {
struct promise_type {
HelloTask get_return_object() { std::cout << "get_return_object" << std::endl; return { }; }
std::suspend_never initial_suspend() { std::cout << "initial_suspend" << std::endl; return {}; }
std::suspend_never final_suspend() noexcept { std::cout << "final_suspend" << std::endl;return {}; }
void return_void() { std::cout << "return_void" << std::endl; }
void unhandled_exception() { std::cout << "unhandled_exception" << std::endl; }
};
};
HelloTask myCoroutine() {
std::cout << "create coroutine" << std::endl;
co_return; // make it a coroutine
}
int main() {
std::cout << "start main" << std::endl;
HelloTask x = myCoroutine();
std::cout << "end main" << std::endl;
}
Nesse caso, a saída é:
O fluxo é o seguinte:
- start main
- O objeto da coroutine é alocado na heap, o promise é construído e promise.get_return_object é chamado, retornando o objeto atual (neste momento foi impresso get_return_object); fazemos isso para salvar o resultado em uma variável local.
- promise.initial_suspend: neste momento imprime initial_suspend; aqui o co_await é disparado, imprime create coroutine e, após executar o corpo da corrotina, chama co_return.
- co_return chama promise.return_void, imprimindo return_void.
- Por fim, promise.final_suspend imprime final_suspend.
- end main
Há ainda muito conteúdo relacionado a corrotinas; esta seção é a abertura da série, e as próximas serão atualizadas continuamente~
Tópicos relacionados¶
- Prática com channel no C++20 | C++那些事
- Concepts (conceitos) no C++20 | C++那些事
- constexpr e a expressão vírgula no C++20 | C++那些事
- Ordem de bytes (endian) no C++20 | C++那些事
- Expressões Lambda no C++20 (parte 1) | C++那些事
- Módulos (Modules) no C++20 | C++那些事
- Biblioteca Ranges do C++20 | C++那些事
- Design e armadilhas do std::span no C++20 | C++那些事
- Guia dos novos recursos do C++20 | C++那些事
- [[likely]] e [[unlikely]] no C++20 | C++那些事