Ekspresi Lambda secara Mendalam
Termasuk bagian Pemrograman Berorientasi Object dalam perjalanan C++ di Coddy. Pelajaran 85 dari 104.
Ekspresi lambda, yang diperkenalkan di C++11, adalah fungsi anonim yang bisa kamu definisikan secara langsung. Kamu sudah melihat lambda dasar dengan algoritma STL, tetapi memahami sintaks lengkapnya membuka kemampuan canggih untuk menangkap variabel dan mengontrol cara variabel tersebut diakses.
Sintaks lambda yang lengkap adalah: [capture](parameters) mutable -> return_type { body }. Klausa capture menentukan variabel luar mana yang dapat diakses lambda dan caranya:
#include <iostream>
int main() {
int x = 10;
int y = 20;
auto byValue = [x]() { return x * 2; }; // Salinan x
auto byRef = [&y]() { y += 5; }; // Referensi ke y
auto allByValue = [=]() { return x + y; }; // Salin semua
auto allByRef = [&]() { x++; y++; }; // Referensi ke semua
auto mixed = [x, &y]() { y += x; }; // Gabungkan keduanya
byRef();
std::cout << y << "\n"; // 25
}Secara default, variabel yang ditangkap berdasarkan nilai bersifat const di dalam lambda. Kata kunci mutable memungkinkan salinan ini diubah:
int counter = 0;
auto increment = [counter]() mutable {
return ++counter; // Mengubah salinan milik lambda
};
std::cout << increment() << "\n"; // 1
std::cout << increment() << "\n"; // 2
std::cout << counter << "\n"; // 0, nilai asli tidak berubahC++14 menambahkan init captures, yang memungkinkan kamu membuat variabel baru atau memindahkan object ke dalam lambda:
auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
return *p;
}; // Kepemilikan dialihkan ke lambdaLambda sangat berguna dalam OOP saat kamu perlu meneruskan perilaku sebagai parameter, misalnya untuk callback, comparator khusus, atau event handler, tanpa mendefinisikan objek fungsi terpisah.
Tantangan
MudahMari buat sistem penanganan event yang menunjukkan keunggulan ekspresi lambda dengan berbagai mode capture. Kamu akan membuat event dispatcher sederhana yang menyimpan dan memanggil callback, untuk menunjukkan cara lambda dapat menangkap state eksternal dengan berbagai cara.
Kamu akan mengatur kodemu dalam tiga file:
EventDispatcher.h: Definisikan classEventDispatcheryang mengelola callback event.Dispatcher-mu harus menyimpan callback menggunakan
std::vectorberisistd::function<void()>. Sertakan method berikut:addCallback(std::function<void()> callback): menambahkan callback ke daftarfireAll(): memanggil semua callback yang tersimpan secara berurutanclear(): menghapus semua callback
Kamu perlu menyertakan
<functional>dan<vector>.EventDispatcher.cpp: Implementasikan method untuk dispatcher-mu. MethodfireAll()cukup mengiterasi semua callback dan memanggil masing-masing.main.cpp: Baca dua input:- Angka basis (integer)
- Pengali (integer)
Buat
EventDispatcherdan tunjukkan berbagai teknik capture lambda dengan menambahkan tiga callback:- Lambda yang menangkap angka basis berdasarkan nilai dan mencetak:
Base value: [base] - Lambda yang menangkap pengali berdasarkan referensi, menaikkan nilainya sebesar 1, lalu mencetak:
Multiplier after increment: [multiplier] - Lambda mutable yang menangkap variabel penghitung (diinisialisasi ke 0) berdasarkan nilai, menaikkan nilainya setiap kali dipanggil, dan mencetak:
Call count: [counter]
Setelah menambahkan semua callback, panggil
fireAll()dua kali untuk melihat perilaku berbagai mode capture dalam beberapa pemanggilan. Di antara dua pemanggilanfireAll(), cetak---sebagai pemisah.Terakhir, setelah kedua putaran, cetak nilai akhir variabel pengali dari main untuk menunjukkan pengaruh capture referensi terhadapnya:
Final multiplier: [multiplier]
Contohnya, dengan input 10 dan 5:
Base value: 10
Multiplier after increment: 6
Call count: 1
---
Base value: 10
Multiplier after increment: 7
Call count: 1
Final multiplier: 7Dengan input 42 dan 0:
Base value: 42
Multiplier after increment: 1
Call count: 1
---
Base value: 42
Multiplier after increment: 2
Call count: 1
Final multiplier: 2Perhatikan perilaku utamanya: capture berdasarkan nilai mempertahankan nilai awal basis, capture berdasarkan referensi mengubah variabel pengali yang sebenarnya di main (nilainya terus bertambah setiap kali dipanggil), dan penghitung lambda mutable menunjukkan 1 di setiap putaran. Itu karena fireAll() mengiterasi callback-nya berdasarkan nilai: setiap putaran memanggil salinan baru dari std::function yang tersimpan. Penghitung yang di-capture salinan tersebut dimulai dari 0 dan bertambah menjadi 1, sehingga state callback yang tersimpan tidak pernah bertambah.
Coba sendiri
#include <iostream>
#include "EventDispatcher.h"
using namespace std;
int main() {
int base;
int multiplier;
cin >> base;
cin >> multiplier;
EventDispatcher dispatcher;
// TODO: Tambahkan lambda yang menangkap base BERDASARKAN NILAI
// Lambda ini harus mencetak: "Base value: [base]"
// TODO: Tambahkan lambda yang menangkap multiplier BERDASARKAN REFERENSI
// Lambda ini harus menaikkan multiplier sebesar 1, lalu mencetak: "Multiplier after increment: [multiplier]"
// TODO: Tambahkan lambda MUTABLE yang menangkap penghitung (diinisialisasi ke 0) berdasarkan nilai
// Lambda ini harus menaikkan penghitung dan mencetak: "Call count: [counter]"
// TODO: Panggil fireAll() untuk menjalankan semua callback
// TODO: Cetak "---" sebagai pemisah
// TODO: Panggil fireAll() lagi
// TODO: Cetak nilai akhir multiplier: "Final multiplier: [multiplier]"
return 0;
}
Pelajaran ini berisi kuis singkat. Mulai pelajarannya untuk menjawab kuis dan melacak progresmu.
Semua pelajaran di Pemrograman Berorientasi Object
1Dasar-Dasar OOP
File EksternalBuild dan Kompilasi C++File Header dan File SourceNamespace dan CakupanPengantar OOP dalam C++class vs objectPointer 'this'method (Fungsi Anggota)Atribut (Anggota Data)Dasar Ctor dan DtorRingkasan: Kalkulator Sederhana4Properti class
Anggota instance vs staticGetter dan setterFungsi anggota constKata kunci mutableMethod dan variabel staticFungsi dan class friendRangkuman, Pengelola Rekening Bank7Pewarisan
Pewarisan DasarTingkat Akses pada PewarisanUrutan Pemanggilan Ctor & DtorPenimpaan methodFungsi Virtual & VTablePewarisan BergandaPewarisan VirtualRangkuman: Hierarki Karyawan2Manajemen Memori
Memori Stack vs HeapPointer dan ReferenceMemori Dinamis (new/delete)Smart Pointer di C++RAII di C++Rangkuman: Pengelola Array Dinamis5Enkapsulasi
Penentu Akses dalam C++Penentu Akses secara MendalamPenyembunyian Informasistruct vs classNested dan Inner classRangkuman, Sistem Catatan Siswa8Polimorfisme
Polimorfisme saat Compile vs RuntimeOverloading FungsiMeninjau Kembali Fungsi VirtualFungsi Virtual MurniKelas AbstrakDesain Interface di C++Casting Dinamis & RTTIRangkuman: Kalkulator Bentuk3Konstruktor & Destruktor
Konstruktor DefaultKonstruktor BerparameterKonstruktor SalinKonstruktor PindahDaftar Inisialisasi KonstruktorKonstruktor DelegasiKupas Tuntas DestruktorAturan Tiga, Lima, dan NolRangkuman: class String6Overloading Operator
Pengantar Overloading OperatorOverloading Operator AritmetikaOverloading Operator PerbandinganOperator StreamOverloading Operator PenugasanOverloading Operator [] dan ()Operator Konversi TipeRingkasan, class Matrix9Template
Template FungsiTemplate classSpesialisasi TemplateTemplate VariadikDasar SFINAE & Type TraitsRangkuman, Container Generik12Fitur Modern C++
Semantik Pemindahan & RvaluePenerusan SempurnaEkspresi Lambda secara Mendalamstd::function & std::bindconstexpr dan constevalPengikatan Terstrukturoptional, variant, anyLatihan sendiri: Compiler C++ online