Ce face co_await, co_yield, co_return
O funcție obișnuită rulează de la început până la return. O corutină se poate suspenda la jumătate și poate fi reluată mai târziu, cu toate variabilele locale păstrate. O funcție devine corutină dacă folosește oricare dintre co_await, co_yield sau co_return.
co_yield valoare produce o valoare și se oprește până când cine o folosește cere următoarea (generatori). co_await expresie suspendă corutina până când operația așteptată este gata (programare asincronă: rețea, fișiere). co_return încheie corutina, opțional cu un rezultat.
C++20 oferă doar mecanismul de bază (antetul <coroutine>): tipul returnat de corutină trebuie să aibă un promise_type care spune ce se întâmplă la pornire, la co_yield, la final. Exemplele de mai jos scriu un astfel de tip minimal. C++23 adaugă std::generator, dar biblioteca libc++ folosită aici nu îl are încă; în practică se folosesc și biblioteci ca cppcoro sau Boost.Asio.
Exemple
Exemplul 1: Generator cu co_yield
numara este o corutină: la fiecare co_yield se oprește și predă valoarea. urmator() o reia până la următorul co_yield. Bucla din corutină nu rulează „dintr-o dată”, ci pas cu pas, la cerere.
#include <coroutine>
#include <print>
struct Generator {
struct promise_type {
int curent = 0;
Generator get_return_object() {
return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(int v) noexcept {
curent = v;
return {};
}
void return_void() noexcept {}
void unhandled_exception() { throw; }
};
explicit Generator(std::coroutine_handle<promise_type> h) : h_(h) {}
Generator(const Generator&) = delete;
~Generator() { h_.destroy(); }
bool urmator() {
h_.resume();
return !h_.done();
}
int valoare() const { return h_.promise().curent; }
private:
std::coroutine_handle<promise_type> h_;
};
Generator fibonacci(int cate) {
int a = 0, b = 1;
for (int i = 0; i < cate; ++i) {
co_yield a;
int urm = a + b;
a = b;
b = urm;
}
}
int main() {
Generator g = fibonacci(10);
while (g.urmator()) {
std::print("{} ", g.valoare());
}
std::println("");
}
Rezultat
0 1 1 2 3 5 8 13 21 34
Exemplul 2: Suspendare cu co_await și rezultat cu co_return
Mesajele arată ordinea reală: corutina pornește, se oprește la co_await, main face altceva, apoi o reia; corutina termină și trimite rezultatul cu co_return.
#include <coroutine>
#include <print>
struct Sarcina {
struct promise_type {
int rezultat = 0;
Sarcina get_return_object() {
return Sarcina{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_value(int v) noexcept { rezultat = v; }
void unhandled_exception() { throw; }
};
explicit Sarcina(std::coroutine_handle<promise_type> h) : h_(h) {}
Sarcina(const Sarcina&) = delete;
~Sarcina() { h_.destroy(); }
void reia() { h_.resume(); }
bool gata() const { return h_.done(); }
int rezultat() const { return h_.promise().rezultat; }
private:
std::coroutine_handle<promise_type> h_;
};
Sarcina calculeaza_comanda() {
std::println("[corutină] pornesc calculul");
co_await std::suspend_always{};
std::println("[corutină] am fost reluată, termin");
co_return 42;
}
int main() {
Sarcina s = calculeaza_comanda();
std::println("[main] corutina s-a oprit, gata = {}", s.gata());
s.reia();
std::println("[main] gata = {}, rezultat = {}", s.gata(), s.rezultat());
}
Rezultat
[corutină] pornesc calculul
[main] corutina s-a oprit, gata = false
[corutină] am fost reluată, termin
[main] gata = true, rezultat = 42
Greșeli frecvente
- Folosești
co_returnsauco_yieldîntr-o funcție care returnează un tip obișnuit (int,void). Tipul returnat trebuie să aibă unpromise_type; altfel compilarea eșuează. - Folosești în corutină referințe la variabile care nu mai există când corutina este reluată (de exemplu parametri primiți prin referință de la un temporar). Corutina își păstrează doar propriile variabile locale; ce era prin referință poate dispărea între timp.
- Uiți să distrugi corutina (
handle.destroy()) când nu s-a terminat singură. Starea ei (variabilele locale) rămâne alocată: scurgere de memorie.
Comenzi înrudite
lambda ([](){}): O funcție fără nume, scrisă direct acolo unde e folosită; poate „captura” variabile din jur.return: Încheie funcția curentă și trimite, opțional, o valoare înapoi celui care a apelat-o.noexcept: Marchează o funcție care promite că nu aruncă excepții; ca operator, verifică la compilare dacă o expresie poate arunca.