Menu

Lambda C++: Penjelasan Fungsi Anonim dengan Contoh

Tulis fungsi kecil inline secara langsung dengan lambda C++: sintaksnya, cara kerja capture, kapan memakai mutable, dan jebakan capture yang dangling yang menjebak semua orang.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Fungsi yang Kamu Tulis di Tempat Kamu Memakainya

Di halaman sebelumnya kamu melihat bagaimana overloading membuat beberapa fungsi bisa memakai satu nama. Namun kadang kamu sama sekali tidak ingin fungsi bernama: kamu butuh sepotong kecil logika sekali saja, tepat di tempat kamu memakainya, dan memberinya nama hanya menambah keramaian. Itulah lambda: fungsi anonim yang bisa kamu definisikan inline.

Lambda punya bentuk empat bagian yang khas:

[capture](parameters) -> return_type { body }

[] adalah tanda bahwa yang kamu lihat adalah lambda. Tipe kembaliannya opsional, karena compiler biasanya menyimpulkannya. Berikut lambda yang paling sederhana:

greet hanyalah variabel (tipenya tidak bisa ditulis, jadi kamu menyimpannya dengan auto) yang bisa kamu panggil dengan (). Lambda dengan parameter bekerja persis seperti fungsi biasa:

Capture: Menjangkau Scope di Sekitarnya

Bagian yang membuat lambda lebih dari sekadar fungsi tanpa nama adalah capture list, yaitu []. Capture list membuat lambda bisa memakai variabel dari scope tempat ia didefinisikan, tidak hanya parameternya sendiri.

Capture by value dengan [x]: lambda mendapat salinannya sendiri, yang dibekukan saat lambda dibuat.

Perhatikan bahwa scale(5) mencetak 50, memakai nilai factor sebesar 10 yang ada saat lambda dibuat. Capture by value mengambil potret.

Capture by reference dengan [&x]: lambda merujuk ke variabel aslinya, melihat perubahan sesudahnya, dan bisa mengubahnya.

Kamu juga bisa menangkap semua yang dipakai lambda dengan [=] (semuanya by value) atau [&] (semuanya by reference). Keduanya praktis, tetapi menuliskannya secara eksplisit, seperti [total] atau [&total], menjelaskan dengan tepat apa yang disentuh lambda dan lebih mudah dipahami.

Jebakan Referensi yang Dangling

Capture by reference sama kuatnya dengan berbahayanya. Referensinya hanya valid selama variabel aslinya masih hidup. Kalau lambda hidup lebih lama daripada yang ditangkapnya, kamu mendapat referensi yang dangling dan undefined behavior: program bisa crash, bisa mencetak sampah, atau terlihat berfungsi secara kebetulan.

Inilah kesalahan klasiknya: mengembalikan lambda yang menangkap variabel lokal by reference.

auto makeCounter() {
    int count = 0;
    return [&count]() { return ++count; };  // BUG: count dies here
}
// The returned lambda now references destroyed memory.

Saat makeCounter selesai, variabel lokal count dihancurkan, tetapi lambda masih memegang referensi ke sana. Memanggil lambda yang dikembalikan itu menyentuh memori yang sudah mati. Solusinya adalah capture by value supaya lambda memiliki keadaannya sendiri:

Patokannya: capture by reference hanya kalau lambda dipakai langsung dan secara lokal (seperti pada algoritma di bawah). Begitu sebuah lambda disimpan, dikembalikan, atau dijalankan belakangan, utamakan capture by value.

mutable dan Tipe Kembalian

Apakah kamu memperhatikan mutable di contoh terakhir tadi? Secara default, capture by value bersifat const di dalam lambda: kamu bisa membaca salinannya tetapi tidak bisa mengubahnya. Menambahkan mutable membuat lambda bisa mengubah salinannya sendiri di antara pemanggilan.

mutable hanya memengaruhi salinan pribadi milik lambda; seen di luar tidak tersentuh, dan memang itulah inti dari capture by value.

Hampir selalu compiler bisa menyimpulkan tipe kembaliannya dengan baik. Kamu hanya perlu menuliskannya dengan -> saat ada ambiguitas, misalnya lambda yang bisa mengembalikan tipe berbeda di cabang yang berbeda:

// Without -> the compiler can't decide between int and double
auto half = [](int n) -> double {
    if (n % 2 == 0) return n / 2;   // int
    return n / 2.0;                 // double
};

Lambda dan Algoritma: Manfaat Sebenarnya

Alasan lambda ditambahkan ke C++ adalah untuk memberikan potongan logika singkat ke algoritma standard library. Sebelum ada lambda, kamu harus menulis fungsi bernama terpisah atau function object yang kaku, jauh dari tempat ia dipakai. Sekarang logikanya tinggal tepat di tempat pemanggilan.

Contoh yang paling umum adalah urutan sorting buatan sendiri:

Capture sangat berguna di sini karena lambda bisa menarik sebuah nilai untuk dijadikan patokan penyaringan atau penghitungan. Contoh ini menghitung berapa banyak angka yang melebihi ambang batas yang dipilih pengguna:

Karena lambda-lambda ini dipakai langsung dan tidak hidup lebih lama daripada fungsi di sekitarnya, capture by reference ([&passMark]) juga aman di sini. Namun by value sama jelasnya dan tidak pernah dangling.

Berikutnya: Pointer

Lambda diam-diam memunculkan pertanyaan yang lebih dalam: saat kamu menangkap [&x], lambda memegang lokasi x, dan lokasi itu hanya valid selama x hidup. Ide itu, yaitu nilai yang merujuk ke tempat sesuatu berada di memori, dan apa yang terjadi saat hal yang ditunjuknya menghilang, persis menjadi topik halaman berikutnya. Kita akan berhadapan langsung dengan pointer: cara mengambil sebuah alamat, cara mengikutinya, dan bagaimana masalah dangling yang baru kamu lihat muncul di seluruh C++.

Pertanyaan yang sering diajukan

Apa itu lambda di C++?

Lambda adalah fungsi anonim yang bisa kamu tulis inline, tepat di tempat kamu memakainya. Sintaksnya [captures](parameters){ body }. Lambda sempurna untuk operasi singkat sekali pakai, seperti pembanding yang kamu berikan ke std::sort, tanpa harus mendeklarasikan fungsi bernama terpisah di tempat lain.

Apa perbedaan capture by value dan by reference di lambda C++?

[x] menangkap salinan x yang dibekukan saat lambda dibuat. [&x] menangkap referensi ke x aslinya, jadi lambda melihat perubahan sesudahnya dan bisa mengubahnya. Pakai [&] hanya selama variabel yang ditangkap dijamin hidup lebih lama daripada lambda-nya, kalau tidak kamu mendapat referensi yang dangling.

Kenapa lambda C++ milikku bilang tidak bisa mengubah variabel yang ditangkap?

Capture by value bersifat const di dalam lambda secara default. Tambahkan kata kunci mutable, misalnya [x]() mutable { x++; }, supaya lambda bisa mengubah salinannya sendiri. Perhatikan bahwa ini hanya mengubah salinan milik lambda, bukan variabel aslinya di luar.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai