Topik ini membahas lambda dan std::function sebagai dua perkakas untuk bekerja dengan fungsi sebagai nilai. Pembahasan mencakup anatomi lambda, capture by value dan by reference, kata kunci mutable, serta penyimpanan callable yang tipenya berbeda-beda dalam satu wadah. Setelah membacanya, kriteria algoritma dapat ditulis langsung pada tempat pemanggilan dan callback dapat disimpan tanpa mendefinisikan kelas fungsi baru.
Anatomi lambda dan closure#
Lambda adalah fungsi tanpa nama yang didefinisikan langsung pada tempat pemanggilannya. Bentuknya diawali tanda kurung siku berisi daftar capture, diikuti parameter, lalu isi fungsinya. Compiler menerjemahkan setiap lambda menjadi sebuah kelas dengan operator(), dan objek yang dibuat dari kelas itu disebut closure, yaitu objek fungsi yang membawa isinya sendiri. Karena berupa objek, lambda dapat disimpan pada variabel, diteruskan sebagai argumen, dan dikembalikan dari fungsi.
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> nilai = {85, 60, 90, 70};
// lambda disimpan pada variabel lalu dipanggil
auto sapa = []() {
std::cout << "Rekap nilai\n";
};
sapa();
// lambda sebagai kriteria sort: urutan menurun
std::sort(nilai.begin(), nilai.end(),
[](int a, int b) { return a > b; });
for (int n : nilai) {
std::cout << n << '\n';
}
return 0;
}Rekap nilai
90
85
70
60Lambda pertama disimpan pada variabel sapa lalu dipanggil seperti fungsi biasa. Lambda kedua diteruskan langsung sebagai argumen ketiga std::sort dan menentukan bahwa elemen a harus mendahului b bila a > b bernilai true, sehingga urutannya menurun. Tipe variabelnya ditulis auto karena tipe closure yang dibuat compiler bersifat unik dan tidak dapat dituliskan secara manual.
Parameter lambda ditulis sebagaimana fungsi biasa, dan tipe kembalinya biasanya disimpulkan dari isi badannya. Bila perlu ditegaskan, tipe kembalian ditulis setelah parameter dengan bentuk -> tipe, misalnya [](int n) -> double { return n / 2.0; }. Lambda yang tidak memcapture apa pun juga dapat dikonversi menjadi pointer fungsi biasa, sehingga ia dapat dipakai pada antarmuka lama yang menuntut pointer fungsi.
Capture by value dan by reference#
Daftar capture mengatur variabel luar mana yang boleh dipakai di dalam lambda. Tanpa capture, lambda hanya melihat parameternya sendiri. Menuliskan nama variabel di dalam tanda kurung siku membuat salinannya dibawa masuk, yaitu capture by value, sedangkan tanda & di depan nama membuat lambda melihat variabel aslinya, yaitu capture by reference. Bentuk ringkasnya [=] untuk semua by value dan [&] untuk semua by reference, tetapi daftar eksplisit lebih disarankan karena memperlihatkan secara langsung variabel mana yang dibawa lambda. Di dalam member function, lambda dapat memcapture this untuk mengakses anggota kelas, dan keberadaan objek kelasnya tetap wajib dijaga sebagaimana capture by reference.
#include <iostream>
int main() {
int dipanggil = 0;
// capture by value: salinan dibuat saat lambda didefinisikan
auto denganNilai = [dipanggil]() {
return dipanggil;
};
// capture by reference: variabel asli yang dilihat
auto denganReferensi = [&dipanggil]() {
return dipanggil;
};
dipanggil = 5;
std::cout << "by value : " << denganNilai() << '\n';
std::cout << "by reference: " << denganReferensi() << '\n';
return 0;
}by value : 0
by reference: 5Salinan by value dibuat pada saat lambda didefinisikan, sehingga perubahan dipanggil sesudahnya tidak terlihat dari dalam lambda. Capture by reference tidak menyalin apa pun dan selalu membaca keadaan terbaru variabel aslinya. Konsekuensinya, capture by reference hanya aman selama variabel yang dirujuk masih hidup. Pola keliru berikut memperlihatkan apa yang terjadi bila syarat itu dilanggar.
// tidak-dijalankan (undefined behavior: referensi menggantung)
auto buatNomor() {
int hitungan = 0;
return [&hitungan]() { return ++hitungan; }; // hitungan mati saat fungsi kembali
}mutable untuk keadaan milik lambda#
Operator pemanggilan pada closure dibuat konstan oleh compiler, sehingga salinan by value di dalamnya tidak boleh diubah. Kata kunci mutable melonggarkan larangan tersebut dan membuat closure menyimpan keadaan yang boleh berubah antarpemanggilan. Pola ini berguna untuk penghitung pemanggilan, penomoran berurutan, dan cache kecil tanpa perlu kelas terpisah.
#include <iostream>
int main() {
int hitungan = 0;
// mutable: salinan hitungan milik lambda boleh berubah
auto nomorBerikutnya = [hitungan]() mutable {
return ++hitungan;
};
std::cout << nomorBerikutnya() << '\n';
std::cout << nomorBerikutnya() << '\n';
std::cout << nomorBerikutnya() << '\n';
std::cout << "Hitungan asli: " << hitungan << '\n';
return 0;
}1
2
3
Hitungan asli: 0Setiap pemanggilan nomorBerikutnya() menaikkan salinan hitungan milik closure, bukan variabel aslinya, sehingga keluarannya berurutan 1, 2, 3 sedangkan hitungan di luar tetap 0. Keadaan itu milik masing-masing closure, sehingga menyalin closure berarti menyalin keadaannya pula dan dua salinan berjalan dengan riwayat yang terpisah.
Penyimpanan callable dengan std::function#
Setiap lambda memiliki tipe closure yang unik, sehingga dua lambda dengan isi berbeda tidak dapat dimasukkan ke satu vector tanpa tipe perantara. std::function dari header <functional> menutup masalah itu lewat penghapusan tipe (type erasure), yaitu teknik membungkus callable apa pun yang tanda tangannya cocok menjadi satu tipe tunggal. Objek std::function<int(int)> misalnya dapat menyimpan lambda, pointer fungsi, maupun objek fungsi buatan sendiri, selama semuanya menerima int dan mengembalikan int.
#include <functional>
#include <iostream>
#include <vector>
int main() {
// callable yang berbeda disimpan dalam satu tipe yang sama
std::vector<std::function<int(int)>> operasi;
operasi.push_back([](int n) { return n + 10; });
operasi.push_back([](int n) { return n * 2; });
int data = 5;
for (const auto& op : operasi) {
data = op(data);
std::cout << data << '\n';
}
return 0;
}15
30Dua lambda berbeda ditambahkan ke vector yang sama dan dipanggil bergantian dengan tanda tangan yang seragam. Fleksibilitas ini ada biayanya, karena std::function berpotensi mengalokasi memori dinamis dan memanggil callable yang dibungkusnya secara tidak langsung. Bila tipe callable sudah pasti pada waktu kompilasi, parameter template atau variabel auto lebih ringan, sedangkan std::function dipilih ketika tipenya perlu ditulis pada antarmuka atau disimpan campur dalam satu wadah.
Kesalahan umum#
Empat kesalahan berikut paling sering muncul pada pemakaian lambda.
- Memakai
[&]sebagai default capture pada lambda yang disimpan lama atau dikirim keluar fungsi. Referensinya mudah menggantung setelah variabel asalnya mati. Capture eksplisit by value lebih aman untuk lambda yang menyeberangi lingkup. - Mengubah capture by value tanpa
mutable. Compiler menolaknya karenaoperator()pada closure bersifat konstan. Tambahkanmutableatau ubah capture menjadi by reference. - Menganggap dua lambda bertipe sama karena bentuknya mirip. Tipe closure-nya berbeda, sehingga satu vector tidak dapat menampung keduanya tanpa tipe perantara. Simpan keduanya dalam
std::functionyang tanda tangannya cocok. - Memanggil
std::functionyang belum diisi. Objek kosong yang dipanggil melempar exceptionstd::bad_function_call. Uji dengan member functionoperator bool()sebelum dipanggil bila keadaannya tidak pasti.
Ringkasan#
- Lambda adalah closure yang dibuat compiler, sehingga ia dapat disimpan pada variabel, diteruskan ke algoritma, dan membawa variabel luar lewat daftar capture.
- Capture by value menyalin keadaan saat lambda didefinisikan dan butuh
mutablebila keadaannya akan berubah, sedangkan capture by reference selalu membaca variabel aslinya dan berisiko menggantung. std::functionmenyimpan callable apa pun dengan tanda tangan yang cocok, dengan biaya pemanggilan yang tidak langsung untuk dipertimbangkan.
Referensi#
- Lambda expressions (since C++11) — cppreference.com — Kaidah lengkap sintaks lambda, capture, dan mutable.
- std::function — cppreference.com — Perilaku wrapper callable dan biayanya.
- Introduction to lambdas (anonymous functions) — LearnCpp — Pengantar lambda dari sisi pemula.
- C++11 Language Extensions — isocpp.org — FAQ resmi komunitas C++, termasuk pertanyaan seputar lambda dan closure.