Apa yang dimaksud dengan ekspresi lambda di C ++ 11?
1488
Apa yang dimaksud dengan ekspresi lambda di C ++ 11? Kapan saya akan menggunakannya? Kelas masalah apa yang mereka pecahkan yang tidak mungkin dilakukan sebelum perkenalan mereka?
Beberapa contoh, dan kasus penggunaan akan bermanfaat.
Saya telah melihat sebuah kasus di mana lambda sangat berguna: Seorang kolega saya melakukan kode yang memiliki jutaan iterasi untuk menyelesaikan masalah optimasi ruang. Algoritma itu jauh lebih cepat ketika menggunakan lambda daripada fungsi yang tepat! Kompilernya adalah Visual C ++ 2013.
sergiol
Jawaban:
1491
Masalah
C ++ termasuk fungsi generik yang berguna seperti std::for_eachdan std::transform, yang bisa sangat berguna. Sayangnya mereka juga bisa sangat rumit untuk digunakan, terutama jika functor yang ingin Anda terapkan adalah unik untuk fungsi tertentu.
#include<algorithm>#include<vector>namespace{struct f {voidoperator()(int){// do something}};}void func(std::vector<int>& v){
f f;
std::for_each(v.begin(), v.end(), f);}
Jika Anda hanya menggunakan f satu kali dan di tempat tertentu itu tampaknya sulit untuk menulis seluruh kelas hanya untuk melakukan sesuatu yang sepele dan satu.
Di C ++ 03 Anda mungkin tergoda untuk menulis sesuatu seperti berikut, untuk menjaga functor lokal:
void func2(std::vector<int>& v){struct{voidoperator()(int){// do something}} f;
std::for_each(v.begin(), v.end(), f);}
namun ini tidak diperbolehkan, ftidak bisa diteruskan ke fungsi templat di C ++ 03.
Solusi baru
C ++ 11 memperkenalkan lambdas memungkinkan Anda untuk menulis fungsi inline, anonim untuk menggantikan struct f. Untuk contoh-contoh kecil yang sederhana, ini bisa lebih bersih untuk dibaca (itu membuat semuanya di satu tempat) dan berpotensi lebih mudah untuk dipertahankan, misalnya dalam bentuk paling sederhana:
void func3(std::vector<int>& v){
std::for_each(v.begin(), v.end(),[](int){/* do something here*/});}
Fungsi Lambda hanyalah gula sintaksis untuk functor anonim.
Jenis pengembalian
Dalam kasus sederhana, tipe pengembalian lambda disimpulkan untuk Anda, misalnya:
void func4(std::vector<double>& v){
std::transform(v.begin(), v.end(), v.begin(),[](double d){return d <0.00001?0: d;});}
namun ketika Anda mulai menulis lambda yang lebih kompleks, Anda akan dengan cepat menemukan kasus di mana tipe pengembalian tidak dapat disimpulkan oleh kompiler, misalnya:
Sejauh ini kami belum menggunakan apa pun selain apa yang diteruskan ke lambda di dalamnya, tetapi kami juga dapat menggunakan variabel lain, dalam lambda. Jika Anda ingin mengakses variabel lain, Anda dapat menggunakan klausa tangkap ( []ekspresi), yang sejauh ini belum digunakan dalam contoh-contoh ini, misalnya:
Anda dapat menangkap dengan referensi dan nilai, yang dapat Anda tentukan menggunakan &dan =masing - masing:
[&epsilon] ditangkap dengan referensi
[&] menangkap semua variabel yang digunakan dalam lambda dengan referensi
[=] menangkap semua variabel yang digunakan dalam lambda berdasarkan nilai
[&, epsilon] menangkap variabel seperti dengan [&], tetapi dengan nilai epsilon
[=, &epsilon] menangkap variabel seperti dengan [=], tetapi epsilon dengan referensi
Yang dihasilkan operator()adalah constsecara default, dengan implikasi yang menangkap akan constketika Anda mengaksesnya secara default. Ini memiliki efek bahwa setiap panggilan dengan input yang sama akan menghasilkan hasil yang sama, namun Anda dapat menandai lambdamutable untuk meminta bahwa operator()yang dihasilkan tidak const.
@ Yakk kamu sudah terjebak. lambdas tanpa tangkapan memiliki konversi implisit ke pointer tipe fungsi. fungsi konversi constselalu ...
Johannes Schaub - litb
2
@ JohannesSchaub-litb oh licik - dan itu terjadi ketika Anda memohon ()- dilewatkan sebagai lambda argumen-nol, tetapi karena () consttidak cocok dengan lambda, itu mencari konversi tipe yang memungkinkannya, yang mencakup pemeran implisit -untuk-fungsi-pointer, dan kemudian menyebutnya! Sneaky!
Yakk - Adam Nevraumont
2
Menarik - Saya awalnya berpikir bahwa lambdas adalah fungsi anonim daripada functors, dan bingung tentang cara kerja tangkapan.
user253751
50
Jika Anda ingin menggunakan lambdas sebagai variabel dalam program Anda, Anda dapat menggunakan: std::function<double(int, bool)> f = [](int a, bool b) -> double { ... }; Tapi biasanya, kami membiarkan kompiler menyimpulkan jenis: auto f = [](int a, bool b) -> double { ... }; (dan jangan lupa #include <functional>)
Evert Heylen
11
Saya kira tidak semua orang mengerti mengapa return d < 0.00001 ? 0 : d;dijamin akan kembali dua kali lipat, ketika salah satu operan adalah konstanta bilangan bulat (itu karena aturan promosi implisit dari? konversi tidak peduli yang mana yang akan diambil). Mengubah ke 0.0 : dmungkin akan membuat contoh lebih mudah dimengerti.
Lundin
831
Apa fungsi lambda?
Konsep C ++ dari fungsi lambda berasal dari kalkulus lambda dan pemrograman fungsional. Lambda adalah fungsi tanpa nama yang berguna (dalam pemrograman aktual, bukan teori) untuk cuplikan kode pendek yang tidak mungkin untuk digunakan kembali dan tidak layak disebut.
Dalam C ++ fungsi lambda didefinisikan seperti ini
[](){}// barebone lambda
atau dengan segala kemuliaan
[]()mutable-> T {}// T is the return type, still lacking throw()
[]adalah daftar tangkap, ()daftar argumen dan{} fungsi tubuh.
Daftar penangkapan
Daftar penangkapan mendefinisikan apa dari luar lambda harus tersedia di dalam fungsi tubuh dan bagaimana. Itu bisa berupa:
nilai: [x]
referensi [& x]
variabel apa pun saat ini dalam ruang lingkup dengan referensi [&]
sama dengan 3, tetapi dengan nilai [=]
Anda dapat mencampur salah satu di atas dalam daftar yang dipisahkan koma [x, &y] .
Daftar argumen
Daftar argumen sama dengan fungsi C ++ lainnya.
Fungsi tubuh
Kode yang akan dieksekusi ketika lambda sebenarnya dipanggil.
Pengurangan tipe pengembalian
Jika lambda hanya memiliki satu pernyataan pengembalian, jenis kembali dapat dihilangkan dan memiliki jenis implisit decltype(return_statement) .
Yg mungkin berubah
Jika lambda ditandai bisa berubah (mis []() mutable { } ) Itu diizinkan untuk mengubah nilai yang telah ditangkap oleh nilai.
Gunakan kasing
Pustaka yang didefinisikan oleh standar ISO sangat diuntungkan oleh lambdas dan meningkatkan kegunaan beberapa bilah karena sekarang pengguna tidak perlu mengacaukan kode mereka dengan functors kecil dalam beberapa lingkup yang dapat diakses.
C ++ 14
Dalam C ++ 14 lambdas telah diperpanjang oleh berbagai proposal.
Inisialisasi Capture Lambda
Elemen daftar tangkapan sekarang dapat diinisialisasi dengan =. Ini memungkinkan penggantian nama variabel dan untuk menangkap dengan memindahkan. Contoh yang diambil dari standar:
int x =4;auto y =[&r = x, x = x+1]()->int{
r +=2;return x+2;}();// Updates ::x to 6, and initializes y to 7.
dan satu diambil dari Wikipedia yang menunjukkan cara menangkap dengan std::move:
auto ptr = std::make_unique<int>(10);// See below for std::make_uniqueauto lambda =[ptr = std::move(ptr)]{return*ptr;};
Lambdas generik
Lambdas sekarang bisa menjadi generik ( autoakan setara dengan di Tsini jika
Tada argumen tipe templat di suatu tempat di lingkup sekitarnya):
auto lambda =[](auto x,auto y){return x + y;};
Peningkatan Pengurangan Jenis Pengembalian
C ++ 14 memungkinkan tipe pengembalian yang disimpulkan untuk setiap fungsi dan tidak membatasi itu ke fungsi formulir return expression;. Ini juga diperluas ke lambdas.
Dalam contoh Anda untuk menangkap lambda yang diinisialisasi di atas, mengapa Anda mengakhiri fungsi lamba dengan () ;? Ini muncul seperti [] () {} (); dari pada [](){};. Bukankah seharusnya nilai x menjadi 5?
Ramakrishnan Kannan
7
@RamakrishnanKannan: 1) the () ada untuk memanggil lambda segera setelah mendefinisikannya dan memberikan nilai pengembaliannya. Variabel y adalah bilangan bulat, bukan lambda. 2) Tidak, x = 5 adalah lokal untuk lambda (tangkapan dengan nilai yang kebetulan memiliki nama yang sama dengan variabel lingkup luar x), dan kemudian x + 2 = 5 + 2 dikembalikan. Penugasan kembali variabel luar x terjadi melalui referensi r:, r = &x; r += 2;tetapi ini terjadi pada nilai asli 4.
The Vee
168
Ekspresi Lambda biasanya digunakan untuk merangkum algoritma sehingga mereka dapat diteruskan ke fungsi lain. Namun, dimungkinkan untuk mengeksekusi lambda segera setelah definisi :
Ini membuat ekspresi lambda alat yang ampuh untuk refactoring fungsi kompleks . Anda mulai dengan membungkus bagian kode dalam fungsi lambda seperti yang ditunjukkan di atas. Proses parameterisasi eksplisit kemudian dapat dilakukan secara bertahap dengan pengujian lanjutan setelah setiap langkah. Setelah Anda memiliki kode-blok sepenuhnya parameter (seperti yang ditunjukkan oleh penghapusan& ), Anda dapat memindahkan kode ke lokasi eksternal dan menjadikannya fungsi normal.
Demikian pula, Anda dapat menggunakan ekspresi lambda untuk menginisialisasi variabel berdasarkan hasil algoritma ...
int a =[](int b ){int r=1;while(b>0) r*=b--;return r;}(5);// 5!
Sebagai cara mempartisi logika program Anda, Anda bahkan mungkin merasa berguna untuk meneruskan ekspresi lambda sebagai argumen ke ekspresi lambda lain ...
Ekspresi Lambda juga memungkinkan Anda membuat fungsi bernama nested , yang bisa menjadi cara yang nyaman untuk menghindari duplikat logika. Menggunakan lambdas bernama juga cenderung sedikit lebih mudah pada mata (dibandingkan dengan lambdas inline anonim) ketika melewati fungsi non-sepele sebagai parameter ke fungsi lain. Catatan: jangan lupa titik koma setelah kurung kurawal tutup.
auto algorithm =[&](double x,double m,double b )->double{return m*x+b;};int a=algorithm(1,2,3), b=algorithm(4,5,6);
Jika profiling berikutnya mengungkapkan overhead inisialisasi yang signifikan untuk objek fungsi, Anda dapat memilih untuk menulis ulang ini sebagai fungsi normal.
Pernahkah Anda menyadari bahwa pertanyaan ini diajukan 1,5 tahun yang lalu dan bahwa aktivitas terakhir hampir 1 tahun yang lalu? Bagaimanapun, Anda menyumbangkan beberapa ide menarik yang belum pernah saya lihat sebelumnya!
Piotr99
7
Terima kasih atas tip define-and-execute secara simultan! Saya pikir perlu dicatat bahwa itu berfungsi sebagai contidion untuk ifpernyataan :, if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespacedengan asumsi iadalahstd::string
Blacklight Shining
74
Jadi berikut ini adalah ekspresi hukum: [](){}();.
nobar
8
Ugh! (lambda: None)()Sintaksis Python jauh lebih mudah dibaca.
dan04
9
@ nobar - Anda benar, saya salah ketik. Ini sah (saya mengujinya kali ini)main() {{{{((([](){{}}())));}}}}
Mark Lakata
38
Jawaban
T: Apa yang dimaksud dengan ekspresi lambda di C ++ 11?
A: Di bawah tenda, itu adalah objek dari kelas yang di-autogenerasi dengan operator kelebihan beban () const . Objek semacam itu disebut penutupan dan dibuat oleh kompiler. Konsep 'closure' ini dekat dengan konsep bind dari C ++ 11. Tetapi lambdas biasanya menghasilkan kode yang lebih baik. Dan panggilan melalui penutupan memungkinkan inlining penuh.
T: Kapan saya akan menggunakannya?
A: Untuk mendefinisikan "logika sederhana dan kecil" dan meminta kompiler melakukan pembangkitan dari pertanyaan sebelumnya. Anda memberikan kompiler beberapa ekspresi yang Anda inginkan di dalam operator (). Semua kompiler hal-hal lain akan menghasilkan untuk Anda.
T: Kelas masalah apa yang mereka pecahkan yang tidak mungkin dilakukan sebelum perkenalan mereka?
A: Ini adalah semacam gula sintaksis seperti operator kelebihan beban alih-alih fungsi untuk penambahan kustom , operasi sub-transaksi ... Tapi itu menyimpan lebih banyak baris kode yang tidak dibutuhkan untuk membungkus 1-3 baris logika nyata ke beberapa kelas, dan lain-lain! Beberapa insinyur berpikir bahwa jika jumlah garis lebih kecil maka ada sedikit kesempatan untuk membuat kesalahan di dalamnya (saya juga berpikir demikian)
Contoh penggunaan
auto x =[=](int arg1){printf("%i", arg1);};void(*f)(int)= x;
f(1);
x(1);
Ekstra tentang lambda, tidak tercakup oleh pertanyaan. Abaikan bagian ini jika Anda tidak tertarik
1. Nilai yang diambil. Apa yang bisa Anda ambil
1.1. Anda bisa merujuk ke variabel dengan durasi penyimpanan statis di lambdas. Mereka semua ditangkap.
1.2. Anda dapat menggunakan lambda untuk menangkap nilai "berdasarkan nilai". Dalam hal demikian vars yang ditangkap akan disalin ke objek fungsi (penutupan).
[captureVar1,captureVar2](int arg1){}
1.3. Anda dapat menangkap referensi menjadi. & - dalam konteks ini berarti referensi, bukan petunjuk.
[&captureVar1,&captureVar2](int arg1){}
1.4. Itu ada notasi untuk menangkap semua vars non-statis dengan nilai, atau dengan referensi
[=](int arg1){}// capture all not-static vars by value[&](int arg1){}// capture all not-static vars by reference
1.5. Itu ada notasi untuk menangkap semua vars non-statis dengan nilai, atau dengan referensi dan tentukan lebih. Contoh: Tangkap semua vars bukan-statis dengan nilai, tetapi dengan menangkap referensi Param2
[=,&Param2](int arg1){}
Tangkap semua vars tidak-statis dengan referensi, tetapi dengan menangkap nilai Param2
[&,Param2](int arg1){}
2. Pengurangan jenis pengembalian
2.1. Jenis return Lambda dapat disimpulkan jika lambda adalah satu ekspresi. Atau Anda dapat menentukannya secara eksplisit.
Jika lambda memiliki lebih dari satu ekspresi, maka tipe kembali harus ditentukan melalui tipe trailing return. Juga, sintaksis serupa dapat diterapkan pada fungsi otomatis dan fungsi anggota
3. Nilai yang diambil. Apa yang tidak bisa Anda tangkap
3.1. Anda hanya dapat menangkap vars lokal, bukan variabel anggota objek.
4. konversi
4.1 !! Lambda bukan penunjuk fungsi dan bukan fungsi anonim, tetapi lambda tangkap-kurang dapat secara implisit dikonversi menjadi penunjuk fungsi.
ps
Lebih lanjut tentang informasi tata bahasa lambda dapat ditemukan dalam Draf Kerja untuk Bahasa Pemrograman C ++ # 337, 2012-01-16, 5.1.2. Ekspresi Lambda, hal.88
Di C ++ 14 fitur tambahan yang dinamai "init capture" telah ditambahkan. Hal ini memungkinkan untuk melakukan deklarasi anggota data penutupan secara militer:
auto toFloat =[](int value){returnfloat(value);};auto interpolate =[min = toFloat(0), max = toFloat(255)](int value)->float{return(value - min)/(max - min);};
[&,=Param2](int arg1){}Tampaknya ini bukan sintaks yang valid. Bentuk yang benar adalah[&,Param2](int arg1){}
GetFree
Terima kasih. Pertama saya mencoba mengkompilasi cuplikan ini. Dan tampaknya asimetri aneh dalam modifikator yang diizinkan dalam daftar tangkapan // g ++ -std = c ++ 11 main.cpp -o test_bin; ./test_bin #include <stdio.h> int main () {#if 1 {int param = 0; otomatis f = [=, & param] (int arg1) bisa berubah {param = arg1;}; f (111); printf ("% i \ n", param); } # endif #jika 0 {int param = 0; otomatis f = [&, = param] (int arg1) bisa berubah {param = arg1;}; f (111); printf ("% i \ n", param); } #endif mengembalikan 0; }
bruziuz
Terlihat bahwa baris baru dalam tidak didukung dalam komentar. Lalu saya membuka ekspresi 5.1.2 Lambda, hal.88, "Draf Kerja, Standar untuk Bahasa Pemrograman C ++", Nomor Dokumen: # 337, 2012-01-16. Dan melihat ke sintaks tata bahasa. Dan kamu benar. Tidak ada yang namanya menangkap via "= arg"
bruziuz
Terima kasih besar, perbaiki deskripsi dan dapatkan pengetahuan baru tentangnya.
bruziuz
16
Fungsi lambda adalah fungsi anonim yang Anda buat sebaris. Ini dapat menangkap variabel seperti yang telah dijelaskan oleh beberapa orang, (mis. Http://www.stroustrup.com/C++11FAQ.html#lambda ) tetapi ada beberapa batasan. Misalnya, jika ada antarmuka panggilan balik seperti ini,
void apply(void(*f)(int)){
f(10);
f(20);
f(30);}
Anda dapat menulis fungsi di tempat untuk menggunakannya seperti yang diteruskan untuk diterapkan di bawah:
int col=0;void output(){
apply([](int data){
cout << data <<((++col %10)?' ':'\n');});}
karena keterbatasan dalam standar C ++ 11. Jika Anda ingin menggunakan tangkapan, Anda harus bergantung pada perpustakaan dan
#include<functional>
(atau pustaka STL lainnya seperti algoritma untuk mendapatkannya secara tidak langsung) dan kemudian bekerja dengan fungsi std :: daripada meneruskan fungsi normal sebagai parameter seperti ini:
alasannya adalah, bahwa lambda hanya dapat dikonversi ke pointer fungsi, jika tidak memiliki tangkapan. jika applytemplat yang menerima functor, itu akan berfungsi
sp2danny
1
Tetapi masalahnya adalah bahwa jika berlaku adalah antarmuka yang ada, Anda mungkin tidak memiliki kemewahan untuk dapat mendeklarasikannya secara berbeda dari fungsi lama yang sederhana. Standar bisa dirancang untuk memungkinkan instance baru dari fungsi lama biasa dihasilkan setiap kali ekspresi lambda dieksekusi, dengan dihasilkan referensi hard-kode untuk variabel yang ditangkap. Tampaknya fungsi lambda dihasilkan pada waktu kompilasi. Ada konsekuensi lain juga. mis., Jika Anda mendeklarasikan variabel statis, bahkan jika Anda mengevaluasi kembali ekspresi lambda, Anda tidak mendapatkan variabel statis baru.
Ted
1
pointer fungsi sering dimaksudkan untuk disimpan, dan penangkapan lambdas dapat keluar dari ruang lingkup. yang hanya menangkap lambdas yang dikonversi menjadi function-pointer adalah desain
sp2danny
1
Anda masih harus memperhatikan tumpukan variabel yang tidak dialokasikan untuk alasan yang sama. Lihat blogs.msdn.com/b/nativeconcurrency/archive/2012/01/29/... Contoh yang saya tulis dengan output dan apply ditulis sehingga jika alih-alih fungsi pointer diizinkan dan digunakan, mereka akan berfungsi juga. Kol tetap dialokasikan sampai setelah semua fungsi panggilan dari berlaku selesai. Bagaimana Anda menulis ulang kode ini agar berfungsi menggunakan antarmuka yang ada? Apakah Anda akan menggunakan variabel global atau statis, atau transformasi kode yang lebih kabur?
Ted
1
atau mungkin Anda hanya bermaksud bahwa ekspresi lambda adalah nilai dan karenanya bersifat sementara, namun kode tetap konstan (tunggal / statis) sehingga dapat dipanggil di masa mendatang. Dalam hal itu, mungkin fungsi tersebut harus tetap dialokasikan selama tangkapan yang dialokasikan tumpukannya tetap dialokasikan. Tentu saja bisa menjadi berantakan jika dibatalkan, misalnya misalnya banyak variasi fungsi dialokasikan dalam satu lingkaran.
Ted
12
Salah satu penjelasan terbaik lambda expressiondiberikan oleh penulis C ++ Bjarne Stroustrup dalam bukunya ***The C++ Programming Language***bab 11 ( ISBN-13: 978-0321563842 ):
What is a lambda expression?
Sebuah ekspresi lambda , kadang-kadang juga disebut sebagai lambda
fungsi atau (ketat berbicara tidak benar, tapi bahasa sehari-hari) sebagai
lambda , adalah notasi yang disederhanakan untuk mendefinisikan dan menggunakan objek fungsi anonim . Alih-alih mendefinisikan kelas bernama dengan operator (), kemudian membuat objek kelas itu, dan akhirnya memohonnya, kita bisa menggunakan singkatan.
When would I use one?
Ini sangat berguna ketika kita ingin meneruskan operasi sebagai argumen ke suatu algoritma. Dalam konteks antarmuka pengguna grafis (dan di tempat lain), operasi seperti itu sering disebut sebagai panggilan balik .
What class of problem do they solve that wasn't possible prior to their introduction?
Di sini saya kira setiap tindakan yang dilakukan dengan ekspresi lambda dapat diselesaikan tanpa mereka, tetapi dengan kode yang lebih banyak dan kompleksitas yang jauh lebih besar. Ekspresi Lambda ini adalah cara optimasi untuk kode Anda dan cara membuatnya lebih menarik. Sedih oleh Stroustup:
cara optimal untuk mengoptimalkan
Some examples
melalui ekspresi lambda
void print_modulo(constvector<int>& v, ostream& os,int m)// output v[i] to os if v[i]%m==0{
for_each(begin(v),end(v),[&os,m](int x){if(x%m==0) os << x <<'\n';});}
atau melalui fungsi
classModulo_print{
ostream& os;// members to hold the capture list int m;public:Modulo_print(ostream& s,int mm):os(s), m(mm){}voidoperator()(int x)const{if(x%m==0) os << x <<'\n';}};
atau bahkan
void print_modulo(constvector<int>& v, ostream& os,int m)// output v[i] to os if v[i]%m==0{classModulo_print{
ostream& os;// members to hold the capture listint m;public:Modulo_print(ostream& s,int mm):os(s), m(mm){}voidoperator()(int x)const{if(x%m==0) os << x <<'\n';}};
for_each(begin(v),end(v),Modulo_print{os,m});}
jika kamu membutuhkannya kamu bisa memberi nama lambda expressionseperti di bawah ini:
void print_modulo(constvector<int>& v, ostream& os,int m)// output v[i] to os if v[i]%m==0{autoModulo_print=[&os,m](int x){if(x%m==0) os << x <<'\n';};
for_each(begin(v),end(v),Modulo_print);}
Atau asumsikan sampel sederhana lain
voidTestFunctions::simpleLambda(){bool sensitive =true;
std::vector<int> v = std::vector<int>({1,33,3,4,5,6,7});
sort(v.begin(),v.end(),[sensitive](int x,int y){
printf("\n%i\n", x < y);return sensitive ? x < y : abs(x)< abs(y);});
printf("sorted");
for_each(v.begin(), v.end(),[](int x){
printf("x - %i;", x);});}
akan menghasilkan selanjutnya
0
1
0
1
0
1
0
1
0
1
0 diurutkanx - 1; x - 3; x - 4; x - 5; x - 6; x - 7; x - 33;
[]- ini adalah daftar tangkap atau lambda introducer: jikalambdas tidak memerlukan akses ke lingkungan lokal mereka, kita dapat menggunakannya.
Kutipan dari buku:
Karakter pertama dari ekspresi lambda selalu [ . Seorang pengantar lambda dapat mengambil berbagai bentuk:
• [] : daftar tangkap kosong. Ini menyiratkan bahwa tidak ada nama lokal dari konteks sekitarnya dapat digunakan dalam tubuh lambda. Untuk ekspresi lambda seperti itu, data diperoleh dari argumen atau dari variabel nonlokal.
• [&] : secara implisit ditangkap dengan referensi. Semua nama lokal dapat digunakan. Semua variabel lokal diakses dengan referensi.
• [=] : secara implisit ditangkap oleh nilai. Semua nama lokal dapat digunakan. Semua nama merujuk pada salinan variabel lokal yang diambil pada titik panggilan ekspresi lambda.
• [daftar tangkap]: penangkapan eksplisit; daftar tangkap adalah daftar nama variabel lokal yang akan ditangkap (yaitu, disimpan dalam objek) dengan referensi atau dengan nilai. Variabel dengan nama yang didahului oleh & ditangkap oleh referensi. Variabel lain ditangkap oleh nilai. Daftar tangkap juga dapat berisi ini dan nama diikuti oleh ... sebagai elemen.
• [&, daftar tangkap] : tangkap secara tersirat dengan merujuk semua variabel lokal dengan nama yang tidak disebutkan dalam daftar. Daftar tangkap dapat berisi ini. Nama yang terdaftar tidak dapat didahului oleh &. Variabel yang disebutkan dalam daftar tangkap ditangkap oleh nilai.
• [=, daftar tangkap] : tangkap secara implisit dengan nilai semua variabel lokal dengan nama yang tidak disebutkan dalam daftar. Daftar tangkap tidak dapat mengandung ini. Nama yang terdaftar harus didahului oleh &. Variabel yang disebutkan dalam daftar tangkap ditangkap oleh referensi.
Perhatikan bahwa nama lokal yang didahului oleh & selalu ditangkap oleh referensi dan nama lokal yang tidak diawali oleh & selalu ditangkap oleh nilai. Hanya menangkap dengan referensi yang memungkinkan modifikasi variabel dalam lingkungan panggilan.
Penjelasan yang bagus. Menggunakan rentang berbasis untuk loop, Anda dapat menghindari lambdas dan memperpendek kodefor (int x : v) { if (x % m == 0) os << x << '\n';}
Dietrich Baumgarten
2
Nah, salah satu penggunaan praktis yang saya temukan adalah mengurangi kode pelat boiler. Sebagai contoh:
void process_z_vec(vector<int>& vec){auto print_2d =[](constvector<int>& board,int bsize){for(int i =0; i<bsize; i++){for(int j=0; j<bsize; j++){
cout << board[bsize*i+j]<<" ";}
cout <<"\n";}};// Do sth with the vec.
print_2d(vec,x_size);// Do sth else with the vec.
print_2d(vec,y_size);//... }
Tanpa lambda, Anda mungkin perlu melakukan sesuatu untuk bsizekasus yang berbeda . Tentu saja Anda bisa membuat fungsi tetapi bagaimana jika Anda ingin membatasi penggunaan dalam lingkup fungsi pengguna jiwa? sifat lambda memenuhi persyaratan ini dan saya menggunakannya untuk kasus itu.
Lambda di c ++ diperlakukan sebagai "fungsi yang tersedia". ya itu benar-benar sedang bepergian, Anda mendefinisikannya; Gunakan; dan saat ruang lingkup fungsi induk selesai, fungsi lambda hilang.
c ++ memperkenalkannya di c ++ 11 dan semua orang mulai menggunakannya seperti di setiap tempat yang memungkinkan. contoh dan apa itu lambda dapat ditemukan di sini https://en.cppreference.com/w/cpp/language/lambda
saya akan menjelaskan yang tidak ada tetapi penting untuk diketahui untuk setiap programmer c ++
Lambda tidak dimaksudkan untuk digunakan di mana-mana dan setiap fungsi tidak dapat diganti dengan lambda. Ini juga bukan yang tercepat dibandingkan dengan fungsi normal. karena memiliki beberapa overhead yang perlu ditangani oleh lambda.
itu pasti akan membantu mengurangi jumlah garis dalam beberapa kasus. itu pada dasarnya dapat digunakan untuk bagian kode, yang dipanggil dalam fungsi yang sama satu kali atau lebih dan potongan kode itu tidak diperlukan di tempat lain sehingga Anda dapat membuat fungsi mandiri untuk itu.
Di bawah ini adalah contoh dasar lambda dan apa yang terjadi di latar belakang.
int main(){int member =10;class __lambda_6_18
{int member;public:inline/*constexpr */intoperator()(int a,int b)const{return a + b + member;}public: __lambda_6_18(int _member): member{_member}{}};
__lambda_6_18 endGame = __lambda_6_18{member};
endGame.operator()(4,5);return0;}
jadi seperti yang Anda lihat, jenis overhead apa yang ditambahkan ketika Anda menggunakannya. jadi itu bukan ide yang baik untuk menggunakannya di mana-mana. itu dapat digunakan di tempat-tempat di mana mereka berlaku.
ya itu benar-benar sedang bepergian, Anda mendefinisikannya; Gunakan; dan ketika ruang lingkup fungsi induk selesai fungsi lambda hilang .. bagaimana jika fungsi mengembalikan lambda ke pemanggil?
Nawaz
1
Ini juga bukan yang tercepat dibandingkan dengan fungsi normal. karena memiliki beberapa overhead yang perlu ditangani oleh lambda. Pernahkah Anda benar - benar menjalankan tolok ukur apa pun untuk mendukung klaim ini ? Sebaliknya, templat lambda + sering menghasilkan kode tercepat.
Anda dapat menginisialisasi anggota const kelas Anda, dengan panggilan ke fungsi yang menetapkan nilainya dengan mengembalikan output sebagai parameter output.
Jawaban:
Masalah
C ++ termasuk fungsi generik yang berguna seperti
std::for_each
danstd::transform
, yang bisa sangat berguna. Sayangnya mereka juga bisa sangat rumit untuk digunakan, terutama jika functor yang ingin Anda terapkan adalah unik untuk fungsi tertentu.Jika Anda hanya menggunakan
f
satu kali dan di tempat tertentu itu tampaknya sulit untuk menulis seluruh kelas hanya untuk melakukan sesuatu yang sepele dan satu.Di C ++ 03 Anda mungkin tergoda untuk menulis sesuatu seperti berikut, untuk menjaga functor lokal:
namun ini tidak diperbolehkan,
f
tidak bisa diteruskan ke fungsi templat di C ++ 03.Solusi baru
C ++ 11 memperkenalkan lambdas memungkinkan Anda untuk menulis fungsi inline, anonim untuk menggantikan
struct f
. Untuk contoh-contoh kecil yang sederhana, ini bisa lebih bersih untuk dibaca (itu membuat semuanya di satu tempat) dan berpotensi lebih mudah untuk dipertahankan, misalnya dalam bentuk paling sederhana:Fungsi Lambda hanyalah gula sintaksis untuk functor anonim.
Jenis pengembalian
Dalam kasus sederhana, tipe pengembalian lambda disimpulkan untuk Anda, misalnya:
namun ketika Anda mulai menulis lambda yang lebih kompleks, Anda akan dengan cepat menemukan kasus di mana tipe pengembalian tidak dapat disimpulkan oleh kompiler, misalnya:
Untuk mengatasi ini, Anda diizinkan untuk secara eksplisit menentukan jenis pengembalian untuk fungsi lambda, menggunakan
-> T
:Variabel "Menangkap"
Sejauh ini kami belum menggunakan apa pun selain apa yang diteruskan ke lambda di dalamnya, tetapi kami juga dapat menggunakan variabel lain, dalam lambda. Jika Anda ingin mengakses variabel lain, Anda dapat menggunakan klausa tangkap (
[]
ekspresi), yang sejauh ini belum digunakan dalam contoh-contoh ini, misalnya:Anda dapat menangkap dengan referensi dan nilai, yang dapat Anda tentukan menggunakan
&
dan=
masing - masing:[&epsilon]
ditangkap dengan referensi[&]
menangkap semua variabel yang digunakan dalam lambda dengan referensi[=]
menangkap semua variabel yang digunakan dalam lambda berdasarkan nilai[&, epsilon]
menangkap variabel seperti dengan [&], tetapi dengan nilai epsilon[=, &epsilon]
menangkap variabel seperti dengan [=], tetapi epsilon dengan referensiYang dihasilkan
operator()
adalahconst
secara default, dengan implikasi yang menangkap akanconst
ketika Anda mengaksesnya secara default. Ini memiliki efek bahwa setiap panggilan dengan input yang sama akan menghasilkan hasil yang sama, namun Anda dapat menandai lambdamutable
untuk meminta bahwaoperator()
yang dihasilkan tidakconst
.sumber
const
selalu ...()
- dilewatkan sebagai lambda argumen-nol, tetapi karena() const
tidak cocok dengan lambda, itu mencari konversi tipe yang memungkinkannya, yang mencakup pemeran implisit -untuk-fungsi-pointer, dan kemudian menyebutnya! Sneaky!std::function<double(int, bool)> f = [](int a, bool b) -> double { ... };
Tapi biasanya, kami membiarkan kompiler menyimpulkan jenis:auto f = [](int a, bool b) -> double { ... };
(dan jangan lupa#include <functional>
)return d < 0.00001 ? 0 : d;
dijamin akan kembali dua kali lipat, ketika salah satu operan adalah konstanta bilangan bulat (itu karena aturan promosi implisit dari? konversi tidak peduli yang mana yang akan diambil). Mengubah ke0.0 : d
mungkin akan membuat contoh lebih mudah dimengerti.Apa fungsi lambda?
Konsep C ++ dari fungsi lambda berasal dari kalkulus lambda dan pemrograman fungsional. Lambda adalah fungsi tanpa nama yang berguna (dalam pemrograman aktual, bukan teori) untuk cuplikan kode pendek yang tidak mungkin untuk digunakan kembali dan tidak layak disebut.
Dalam C ++ fungsi lambda didefinisikan seperti ini
atau dengan segala kemuliaan
[]
adalah daftar tangkap,()
daftar argumen dan{}
fungsi tubuh.Daftar penangkapan
Daftar penangkapan mendefinisikan apa dari luar lambda harus tersedia di dalam fungsi tubuh dan bagaimana. Itu bisa berupa:
Anda dapat mencampur salah satu di atas dalam daftar yang dipisahkan koma
[x, &y]
.Daftar argumen
Daftar argumen sama dengan fungsi C ++ lainnya.
Fungsi tubuh
Kode yang akan dieksekusi ketika lambda sebenarnya dipanggil.
Pengurangan tipe pengembalian
Jika lambda hanya memiliki satu pernyataan pengembalian, jenis kembali dapat dihilangkan dan memiliki jenis implisit
decltype(return_statement)
.Yg mungkin berubah
Jika lambda ditandai bisa berubah (mis
[]() mutable { }
) Itu diizinkan untuk mengubah nilai yang telah ditangkap oleh nilai.Gunakan kasing
Pustaka yang didefinisikan oleh standar ISO sangat diuntungkan oleh lambdas dan meningkatkan kegunaan beberapa bilah karena sekarang pengguna tidak perlu mengacaukan kode mereka dengan functors kecil dalam beberapa lingkup yang dapat diakses.
C ++ 14
Dalam C ++ 14 lambdas telah diperpanjang oleh berbagai proposal.
Inisialisasi Capture Lambda
Elemen daftar tangkapan sekarang dapat diinisialisasi dengan
=
. Ini memungkinkan penggantian nama variabel dan untuk menangkap dengan memindahkan. Contoh yang diambil dari standar:dan satu diambil dari Wikipedia yang menunjukkan cara menangkap dengan
std::move
:Lambdas generik
Lambdas sekarang bisa menjadi generik (
auto
akan setara dengan diT
sini jikaT
ada argumen tipe templat di suatu tempat di lingkup sekitarnya):Peningkatan Pengurangan Jenis Pengembalian
C ++ 14 memungkinkan tipe pengembalian yang disimpulkan untuk setiap fungsi dan tidak membatasi itu ke fungsi formulir
return expression;
. Ini juga diperluas ke lambdas.sumber
r = &x; r += 2;
tetapi ini terjadi pada nilai asli 4.Ekspresi Lambda biasanya digunakan untuk merangkum algoritma sehingga mereka dapat diteruskan ke fungsi lain. Namun, dimungkinkan untuk mengeksekusi lambda segera setelah definisi :
secara fungsional setara dengan
Ini membuat ekspresi lambda alat yang ampuh untuk refactoring fungsi kompleks . Anda mulai dengan membungkus bagian kode dalam fungsi lambda seperti yang ditunjukkan di atas. Proses parameterisasi eksplisit kemudian dapat dilakukan secara bertahap dengan pengujian lanjutan setelah setiap langkah. Setelah Anda memiliki kode-blok sepenuhnya parameter (seperti yang ditunjukkan oleh penghapusan
&
), Anda dapat memindahkan kode ke lokasi eksternal dan menjadikannya fungsi normal.Demikian pula, Anda dapat menggunakan ekspresi lambda untuk menginisialisasi variabel berdasarkan hasil algoritma ...
Sebagai cara mempartisi logika program Anda, Anda bahkan mungkin merasa berguna untuk meneruskan ekspresi lambda sebagai argumen ke ekspresi lambda lain ...
Ekspresi Lambda juga memungkinkan Anda membuat fungsi bernama nested , yang bisa menjadi cara yang nyaman untuk menghindari duplikat logika. Menggunakan lambdas bernama juga cenderung sedikit lebih mudah pada mata (dibandingkan dengan lambdas inline anonim) ketika melewati fungsi non-sepele sebagai parameter ke fungsi lain. Catatan: jangan lupa titik koma setelah kurung kurawal tutup.
Jika profiling berikutnya mengungkapkan overhead inisialisasi yang signifikan untuk objek fungsi, Anda dapat memilih untuk menulis ulang ini sebagai fungsi normal.
sumber
if
pernyataan :,if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespace
dengan asumsii
adalahstd::string
[](){}();
.(lambda: None)()
Sintaksis Python jauh lebih mudah dibaca.main() {{{{((([](){{}}())));}}}}
Jawaban
T: Apa yang dimaksud dengan ekspresi lambda di C ++ 11?
A: Di bawah tenda, itu adalah objek dari kelas yang di-autogenerasi dengan operator kelebihan beban () const . Objek semacam itu disebut penutupan dan dibuat oleh kompiler. Konsep 'closure' ini dekat dengan konsep bind dari C ++ 11. Tetapi lambdas biasanya menghasilkan kode yang lebih baik. Dan panggilan melalui penutupan memungkinkan inlining penuh.
T: Kapan saya akan menggunakannya?
A: Untuk mendefinisikan "logika sederhana dan kecil" dan meminta kompiler melakukan pembangkitan dari pertanyaan sebelumnya. Anda memberikan kompiler beberapa ekspresi yang Anda inginkan di dalam operator (). Semua kompiler hal-hal lain akan menghasilkan untuk Anda.
T: Kelas masalah apa yang mereka pecahkan yang tidak mungkin dilakukan sebelum perkenalan mereka?
A: Ini adalah semacam gula sintaksis seperti operator kelebihan beban alih-alih fungsi untuk penambahan kustom , operasi sub-transaksi ... Tapi itu menyimpan lebih banyak baris kode yang tidak dibutuhkan untuk membungkus 1-3 baris logika nyata ke beberapa kelas, dan lain-lain! Beberapa insinyur berpikir bahwa jika jumlah garis lebih kecil maka ada sedikit kesempatan untuk membuat kesalahan di dalamnya (saya juga berpikir demikian)
Contoh penggunaan
Ekstra tentang lambda, tidak tercakup oleh pertanyaan. Abaikan bagian ini jika Anda tidak tertarik
1. Nilai yang diambil. Apa yang bisa Anda ambil
1.1. Anda bisa merujuk ke variabel dengan durasi penyimpanan statis di lambdas. Mereka semua ditangkap.
1.2. Anda dapat menggunakan lambda untuk menangkap nilai "berdasarkan nilai". Dalam hal demikian vars yang ditangkap akan disalin ke objek fungsi (penutupan).
1.3. Anda dapat menangkap referensi menjadi. & - dalam konteks ini berarti referensi, bukan petunjuk.
1.4. Itu ada notasi untuk menangkap semua vars non-statis dengan nilai, atau dengan referensi
1.5. Itu ada notasi untuk menangkap semua vars non-statis dengan nilai, atau dengan referensi dan tentukan lebih. Contoh: Tangkap semua vars bukan-statis dengan nilai, tetapi dengan menangkap referensi Param2
Tangkap semua vars tidak-statis dengan referensi, tetapi dengan menangkap nilai Param2
2. Pengurangan jenis pengembalian
2.1. Jenis return Lambda dapat disimpulkan jika lambda adalah satu ekspresi. Atau Anda dapat menentukannya secara eksplisit.
Jika lambda memiliki lebih dari satu ekspresi, maka tipe kembali harus ditentukan melalui tipe trailing return. Juga, sintaksis serupa dapat diterapkan pada fungsi otomatis dan fungsi anggota
3. Nilai yang diambil. Apa yang tidak bisa Anda tangkap
3.1. Anda hanya dapat menangkap vars lokal, bukan variabel anggota objek.
4. konversi
4.1 !! Lambda bukan penunjuk fungsi dan bukan fungsi anonim, tetapi lambda tangkap-kurang dapat secara implisit dikonversi menjadi penunjuk fungsi.
ps
Lebih lanjut tentang informasi tata bahasa lambda dapat ditemukan dalam Draf Kerja untuk Bahasa Pemrograman C ++ # 337, 2012-01-16, 5.1.2. Ekspresi Lambda, hal.88
Di C ++ 14 fitur tambahan yang dinamai "init capture" telah ditambahkan. Hal ini memungkinkan untuk melakukan deklarasi anggota data penutupan secara militer:
sumber
[&,=Param2](int arg1){}
Tampaknya ini bukan sintaks yang valid. Bentuk yang benar adalah[&,Param2](int arg1){}
Fungsi lambda adalah fungsi anonim yang Anda buat sebaris. Ini dapat menangkap variabel seperti yang telah dijelaskan oleh beberapa orang, (mis. Http://www.stroustrup.com/C++11FAQ.html#lambda ) tetapi ada beberapa batasan. Misalnya, jika ada antarmuka panggilan balik seperti ini,
Anda dapat menulis fungsi di tempat untuk menggunakannya seperti yang diteruskan untuk diterapkan di bawah:
Tetapi Anda tidak bisa melakukan ini:
karena keterbatasan dalam standar C ++ 11. Jika Anda ingin menggunakan tangkapan, Anda harus bergantung pada perpustakaan dan
(atau pustaka STL lainnya seperti algoritma untuk mendapatkannya secara tidak langsung) dan kemudian bekerja dengan fungsi std :: daripada meneruskan fungsi normal sebagai parameter seperti ini:
sumber
apply
templat yang menerima functor, itu akan berfungsiSalah satu penjelasan terbaik
lambda expression
diberikan oleh penulis C ++ Bjarne Stroustrup dalam bukunya***The C++ Programming Language***
bab 11 ( ISBN-13: 978-0321563842 ):What is a lambda expression?
When would I use one?
What class of problem do they solve that wasn't possible prior to their introduction?
Di sini saya kira setiap tindakan yang dilakukan dengan ekspresi lambda dapat diselesaikan tanpa mereka, tetapi dengan kode yang lebih banyak dan kompleksitas yang jauh lebih besar. Ekspresi Lambda ini adalah cara optimasi untuk kode Anda dan cara membuatnya lebih menarik. Sedih oleh Stroustup:
Some examples
melalui ekspresi lambda
atau melalui fungsi
atau bahkan
jika kamu membutuhkannya kamu bisa memberi nama
lambda expression
seperti di bawah ini:Atau asumsikan sampel sederhana lain
akan menghasilkan selanjutnya
[]
- ini adalah daftar tangkap ataulambda introducer
: jikalambdas
tidak memerlukan akses ke lingkungan lokal mereka, kita dapat menggunakannya.Kutipan dari buku:
Additional
Lambda expression
formatReferensi tambahan:
sumber
for (int x : v) { if (x % m == 0) os << x << '\n';}
Nah, salah satu penggunaan praktis yang saya temukan adalah mengurangi kode pelat boiler. Sebagai contoh:
Tanpa lambda, Anda mungkin perlu melakukan sesuatu untuk
bsize
kasus yang berbeda . Tentu saja Anda bisa membuat fungsi tetapi bagaimana jika Anda ingin membatasi penggunaan dalam lingkup fungsi pengguna jiwa? sifat lambda memenuhi persyaratan ini dan saya menggunakannya untuk kasus itu.sumber
Lambda di c ++ diperlakukan sebagai "fungsi yang tersedia". ya itu benar-benar sedang bepergian, Anda mendefinisikannya; Gunakan; dan saat ruang lingkup fungsi induk selesai, fungsi lambda hilang.
c ++ memperkenalkannya di c ++ 11 dan semua orang mulai menggunakannya seperti di setiap tempat yang memungkinkan. contoh dan apa itu lambda dapat ditemukan di sini https://en.cppreference.com/w/cpp/language/lambda
saya akan menjelaskan yang tidak ada tetapi penting untuk diketahui untuk setiap programmer c ++
Lambda tidak dimaksudkan untuk digunakan di mana-mana dan setiap fungsi tidak dapat diganti dengan lambda. Ini juga bukan yang tercepat dibandingkan dengan fungsi normal. karena memiliki beberapa overhead yang perlu ditangani oleh lambda.
itu pasti akan membantu mengurangi jumlah garis dalam beberapa kasus. itu pada dasarnya dapat digunakan untuk bagian kode, yang dipanggil dalam fungsi yang sama satu kali atau lebih dan potongan kode itu tidak diperlukan di tempat lain sehingga Anda dapat membuat fungsi mandiri untuk itu.
Di bawah ini adalah contoh dasar lambda dan apa yang terjadi di latar belakang.
Kode pengguna:
Bagaimana kompilasi mengembangkannya:
jadi seperti yang Anda lihat, jenis overhead apa yang ditambahkan ketika Anda menggunakannya. jadi itu bukan ide yang baik untuk menggunakannya di mana-mana. itu dapat digunakan di tempat-tempat di mana mereka berlaku.
sumber
Satu masalah yang dipecahkannya: Kode lebih sederhana daripada lambda untuk panggilan dalam konstruktor yang menggunakan fungsi parameter output untuk menginisialisasi anggota const
Anda dapat menginisialisasi anggota const kelas Anda, dengan panggilan ke fungsi yang menetapkan nilainya dengan mengembalikan output sebagai parameter output.
sumber