Sebuah function biasa punya aturan tegas: sekali dipanggil, ia berjalan sampai selesai, lalu seluruh variabel lokalnya dihapus dari memori. Coroutine melonggarkan aturan ini — fungsi bisa berhenti sementara di tengah jalan, menyerahkan satu nilai ke pemanggil, lalu dilanjutkan tepat dari titik berhentinya dengan state lokal masih utuh. C++20 membawa tiga kata kunci untuk itu (co_yield, co_await, co_return), tetapi pada awalnya pemakaiannya mengharuskan kita menulis mesin bernama promise type yang panjang dan penuh detail. C++23 mengubah cerita dengan std::generator: return type siap pakai yang menyembunyikan seluruh mesin itu, sehingga generator deret bisa ditulis dalam beberapa baris — dan di toolchain course ini (g++ 14.2) semuanya langsung jalan.
Tiga Kata Kunci Coroutine #
Aturan standarnya sederhana: function yang memuat salah satu kata kunci berikut otomatis menjadi coroutine, dan compiler menuntut return type yang mendukung (misalnya std::generator untuk dua kata kunci pertama).
| Kata kunci | Peran | Return type lazim |
|---|---|---|
co_yield | Serahkan satu nilai, lalu berhenti sampai diminta lagi | std::generator<T> |
co_return | Akhiri coroutine (deret selesai / tugas selesai) | std::generator<T>, std::expected, task type |
co_await | Tunggu operasi asinkron selesai tanpa memblok thread | task/awaitable dari pustaka |
co_yield dan co_return sudah cukup untuk chapter ini; co_await adalah pintu ke pemrograman asinkron (I/O jaringan, task) yang membutuhkan pustaka tambahan, jadi cukup kita kenali bentuknya.
Deret Malas dengan std::generator #
Program berikut memuat tiga generator: deret kuadrat berhingga, deret Fibonacci tak hingga, dan pemecah kalimat menjadi aliran kata. Ketiganya diiterasi seperti container biasa dan bahkan bisa dirangkai dengan views.
// pengantar-coroutines.cpp — std::generator: deret malas tanpa promise type manual
// Kompilasi: g++ -std=c++23 -Wall -Wextra pengantar-coroutines.cpp -o coroutines
#include <generator>
#include <print>
#include <ranges>
#include <string>
#include <string_view>
// Generator berhingga: co_yield menyerahkan satu nilai, lalu fungsi berhenti
std::generator<int> kuadrat(int n) {
for (int i = 1; i <= n; ++i) {
co_yield i * i; // keluarkan nilai, jeda di sini sampai diminta lagi
}
// akhir badan fungsi = co_return implisit: deret selesai
}
// Generator tak hingga: state lokal tetap hidup antar pemanggilan
std::generator<long long> fibonacci() {
long long a = 0, b = 1;
while (true) {
co_yield a;
long long berikut = a + b;
a = b;
b = berikut;
}
}
// Generator string: memecah kalimat menjadi aliran kata
std::generator<std::string> kata(std::string_view kalimat) {
std::string sedangDibaca;
for (char c : kalimat) {
if (c == ' ') {
if (!sedangDibaca.empty()) {
co_yield sedangDibaca;
}
sedangDibaca.clear();
} else {
sedangDibaca += c;
}
}
if (!sedangDibaca.empty()) {
co_yield sedangDibaca; // kata terakhir tanpa spasi di belakang
}
}
int main() {
std::print("kuadrat 1-5 :");
for (int q : kuadrat(5)) {
std::print(" {}", q);
}
std::print("\n");
// Deret tak hingga aman dipakai: kita ambil secukupnya lewat view
std::print("fibonacci :");
for (long long f : fibonacci() | std::views::take(10)) {
std::print(" {}", f);
}
std::print("\n");
// Pipeline: generator + view menghasilkan panjang tiap kata
std::print("panjang kata:");
for (std::size_t n : kata("belajar cpp modern dengan tenang")
| std::views::transform([](const std::string& k) {
return k.size();
})) {
std::print(" {}", n);
}
std::print("\n");
return 0;
}Output yang Dihasilkan:
kuadrat 1-5 : 1 4 9 16 25
fibonacci : 0 1 1 2 3 5 8 13 21 34
panjang kata: 7 3 6 6 6Mari kita bedah:
- Baris 11-16 (kuadrat): return type
std::generator<int>dan satuco_yielddi dalamnya — itu saja. Setiap kalimainmeminta elemen berikutnya, fungsi berjalan hinggaco_yield i * i, menyerahkan nilai, lalu membeku di baris itu; pemanggilan berikutnya melanjutkan dari for yang sama. - Baris 15: akhir badan fungsi berarti
co_returnimplisit — generator menyatakan deret selesai, dan range-for dimainberhenti rapi. - Baris 19-27 (fibonacci): loop
while (true)membuat deret tak hingga, dan variabela,btetap hidup di antara dua permintaan nilai — inilah pembeda utama dengan function biasa. Tidak ada angka yang dihitung sebelum diminta: pemanggilanfibonacci()sendiri belum mengeksekusi apa pun. - Baris 56 (take(10)): deret tak hingga menjadi aman karena
views::takemenghentikan permintaan setelah sepuluh elemen; coroutine dihentikan dengan bersih ketika objek generator-nya dihapus di akhir loop. - Baris 30-45 (kata): generator juga cocok sebagai pembangkit aliran — di sini kalimat dipecah menjadi kata satu per satu, tanpa menampung seluruh hasil ke vector terlebih dahulu.
- Baris 63-67 (pipeline):
kata(...)diperlakukan sebagai range, lalu dirantai denganviews::transform— coroutines dan ranges memang dirancang saling melengkapi, dan hasilnya tercetak7 3 6 6 6(panjang tiap kata “belajar cpp modern dengan tenang”).
Di Balik Layar: Promise Type, Sekilas #
Sebelum C++23, return type generator harus kita bangun sendiri, dan inti bangunannya bernama promise type — sebuah class yang diperiksa compiler untuk mengatur tiga momen: coroutine dimulai, co_yield/co_return dieksekusi, dan coroutine selesai. Versi yang disederhanakan sekali:
template <typename T>
struct promise_type {
T nilaiSaatIni;
std::generator<T> get_return_object(); // objek yang dipegang pemanggil
std::suspend_always yield_value(T nilai) { // dijalankan tiap co_yield
nilaiSaatIni = nilai; // simpan nilai yang diserahkan
return {}; // lalu JEDA coroutine di sini
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {} // dijalankan saat co_return
void unhandled_exception() { std::terminate(); }
};Dua hal perlu digarisbawahi: yield_value adalah penerima co_yield — ia menyimpan nilai lalu mengembalikan awaiter yang menyuruh coroutine berhenti; dan get_return_object adalah penghubung ke objek generator yang dipegang pemanggil. Detailnya (handle, iterator, alokasi frame) panjang dan mudah salah — itulah kenapa std::generator berharga: seluruh bagian di atas sudah ditulis, diuji, dan dioptimalkan oleh pustaka standar.
Kapan Coroutines Layak Dipakai #
Tiga kegunaan paling umum di kode nyata: deret malas (angka prima berikutnya, baris berikutnya dari log) yang tidak boleh dihitung semuanya di depan; pipeline transformasi data di mana tahapan filter/transform dirangkai seperti pada contoh kata + transform; dan operasi asinkron lewat co_await — gaya menulis kode jaringan yang terbaca berurutan tanpa callback berlapis. Untuk dua kegunaan pertama, std::generator sudah cukup; untuk yang ketiga, kita menunggu task type standar atau memakai pustaka eksternal, dan co_await-nya mengikuti prinsip yang sama seperti yang sudah kita lihat.
Generator memberi kita cara baru menata aliran data — berhenti, lanjut, dan menyerahkan nilai di tengah jalan. Berikutnya kita pindah ke perubahan yang bekerja di level paling dasar: cara compiler menemukan dan menghubungkan kode lewat modules, fitur besar yang menantang era header #include.
Rujukan resmi: cppreference — coroutines , cppreference — std::generator .