Ir para o conteúdo

Ú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:

g++ hello.cpp -fcoroutines -std=c++20

É 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:

HelloTask myCoroutine() {
    co_return; // make it a coroutine
}

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 é:

start main
get_return_object
initial_suspend
create coroutine
return_void
final_suspend
end main

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

Comentários