Derinlemesine Lambda İfadeleri
Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 85 / 104.
C++11 ile tanıtılan Lambda ifadeleri, satır içinde (inline) tanımlayabileceğiniz anonim fonksiyonlardır. STL algoritmalarıyla temel lambdaları görmüş olsanız da, tam sözdizimlerini anlamak; değişkenleri yakalamak ve onlara nasıl erişildiğini kontrol etmek için güçlü yeteneklerin kapısını açar.
Tam lambda sözdizimi şöyledir: [capture](parameters) mutable -> return_type { body }. Yakalama ifadesi (capture clause), lambdanın hangi dış değişkenlere erişebileceğini ve nasıl erişeceğini belirler:
#include <iostream>
int main() {
int x = 10;
int y = 20;
auto byValue = [x]() { return x * 2; }; // x'in kopyası
auto byRef = [&y]() { y += 5; }; // y'ye referans
auto allByValue = [=]() { return x + y; }; // Hepsini kopyala
auto allByRef = [&]() { x++; y++; }; // Hepsine referans
auto mixed = [x, &y]() { y += x; }; // İkisini karıştır
byRef();
std::cout << y << "\n"; // 25
}Varsayılan olarak, değer ile yakalanan (captured-by-value) değişkenler lambda içinde const'tur. mutable anahtar kelimesi bu kopyaların değiştirilmesine izin verir:
int counter = 0;
auto increment = [counter]() mutable {
return ++counter; // Lambda'nın kopyasını değiştirir
};
std::cout << increment() << "\n"; // 1
std::cout << increment() << "\n"; // 2
std::cout << counter << "\n"; // 0 - orijinal değişmediC++14, yeni değişkenler oluşturmanıza veya nesneleri lambda içine taşımanıza olanak tanıyan başlatma yakalamalarını (init captures) ekledi:
auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
return *p;
}; // Sahiplik lambda içine aktarıldıLambdalar; geri çağırmalar (callbacks), özel karşılaştırıcılar veya olay işleyiciler için davranışı bir parametre olarak aktarmanız gerektiğinde, ayrı fonksiyon nesneleri tanımlamanıza gerek kalmadan OOP'de özellikle yararlıdır.
Görev
KolayFarklı yakalama modlarına sahip lambda ifadelerinin gücünü gösteren bir olay işleyici (event handler) sistemi kuralım. Geri çağırmaları (callbacks) depolayan ve çağıran basit bir olay dağıtıcı (event dispatcher) oluşturacak ve lambdaların harici durumları çeşitli şekillerde nasıl yakalayabildiğini göstereceksiniz.
Kodunuzu üç dosya halinde düzenleyeceksiniz:
EventDispatcher.h: Olay geri çağırmalarını yöneten birEventDispatchersınıfı tanımlayın.Dağıtıcınız, geri çağırmaları
std::function<void()>türünde birstd::vectorkullanarak saklamalıdır. Şu metotları ekleyin:addCallback(std::function<void()> callback)— listeye bir geri çağırma eklerfireAll()— saklanan tüm geri çağırmaları sırayla çağırırclear()— tüm geri çağırmaları kaldırır
<functional>ve<vector>kütüphanelerini dahil etmeniz gerekecektir.EventDispatcher.cpp: Dağıtıcınız için metotları uygulayın.fireAll()metodu basitçe tüm geri çağırmalar üzerinde döngü kurmalı ve her birini çağırmalıdır.main.cpp: İki girdi okuyun:- Bir taban sayı (tam sayı)
- Bir çarpan (tam sayı)
Bir
EventDispatcheroluşturun ve üç geri çağırma ekleyerek farklı lambda yakalama tekniklerini gösterin:- Taban sayıyı değer ile (by value) yakalayan ve şunu yazdıran bir lambda:
Base value: [base] - Çarpanı referans ile (by reference) yakalayan, değerini 1 artıran ve ardından şunu yazdıran bir lambda:
Multiplier after increment: [multiplier] - 0 ile başlatılmış bir sayaç (counter) değişkenini değer ile yakalayan, her çağrıldığında onu artıran ve şunu yazdıran bir mutable lambda:
Call count: [counter]
Tüm geri çağırmaları ekledikten sonra, farklı yakalama modlarının birden fazla çağrı boyunca nasıl davrandığını görmek için
fireAll()metodunu iki kez çağırın. İkifireAll()çağrısı arasında ayırıcı olarak---yazdırın.Son olarak, her iki turdan sonra, referans yakalamanın onu nasıl etkilediğini göstermek için main içindeki çarpan değişkeninin son değerini yazdırın:
Final multiplier: [multiplier]
Örneğin, 10 ve 5 girdileriyle:
Base value: 10
Multiplier after increment: 6
Call count: 1
---
Base value: 10
Multiplier after increment: 7
Call count: 1
Final multiplier: 742 ve 0 girdileriyle:
Base value: 42
Multiplier after increment: 1
Call count: 1
---
Base value: 42
Multiplier after increment: 2
Call count: 1
Final multiplier: 2Temel davranışlara dikkat edin: değer ile yakalama orijinal taban sayıyı değiştirmez, referans ile yakalama main içindeki gerçek çarpan değişkenini değiştirir (çağrılar boyunca birikir) ve mutable lambdanın sayacı her turda 1 değerini okur. Bunun nedeni fireAll() metodunun geri çağırmaları değer ile döndürmesidir: her tur, saklanan std::function'ın yeni bir kopyasını çağırır; bu kopyanın yakalanan sayacı 0'dan başlar ve 1'e artırılır, bu nedenle saklanan geri çağırmanın kendi durumu asla ilerlemez.
Kendin dene
#include <iostream>
#include "EventDispatcher.h"
using namespace std;
int main() {
int base;
int multiplier;
cin >> base;
cin >> multiplier;
EventDispatcher dispatcher;
// TODO: base değişkenini DEĞER İLE yakalayan bir lambda ekleyin
// Şunu yazdırmalı: "Base value: [base]"
// TODO: multiplier değişkenini REFERANS İLE yakalayan bir lambda ekleyin
// multiplier değerini 1 artırmalı, ardından şunu yazdırmalı: "Multiplier after increment: [multiplier]"
// TODO: Bir sayacı (0 ile başlatılmış) değer ile yakalayan MUTABLE bir lambda ekleyin
// Sayacı artırmalı ve şunu yazdırmalı: "Call count: [counter]"
// TODO: Tüm geri çağırmaları (callbacks) tetiklemek için fireAll() fonksiyonunu çağırın
// TODO: Ayırıcı olarak "---" yazdırın
// TODO: fireAll() fonksiyonunu tekrar çağırın
// TODO: multiplier değişkeninin son değerini yazdırın: "Final multiplier: [multiplier]"
return 0;
}Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.
Nesne Yönelimli Programlama bölümündeki tüm dersler
1OOP Temelleri
Harici DosyalarC++ Build ve DerlemeBaşlık Dosyaları ve Kaynak DosyalarıAd Alanları ve KapsamC++'ta OOP'ye GirişSınıflar ve Nesneler'this' İşaretçisiMetotlar (Üye Fonksiyonlar)Öznitelikler (Veri Üyeleri)Ctor ve Dtor TemelleriÖzet - Basit Hesap Makinesi4Sınıf Özellikleri
Örnek ve Statik ÜyelerGetter ve Setter MetotlarıConst Üye FonksiyonlarMutable Anahtar KelimesiStatik Metotlar ve DeğişkenlerFriend Fonksiyonlar ve SınıflarÖzet - Banka Hesabı Yöneticisi7Kalıtım
Temel KalıtımKalıtım Erişim SeviyeleriCtor ve Dtor Çağrılma SırasıMetot Geçersiz KılmaSanal Fonksiyonlar ve VTableÇoklu KalıtımSanal KalıtımÖzet - Çalışan Hiyerarşisi10STL Genel Bakış
STL Genel Bakış ve FelsefesiSTL KonteynerleriİteratörlerSTL AlgoritmalarıFunctor'lar ve Lambda İfadeleriÖzet - Kelime Frekansı13Tasarım Kalıpları 1. Bölüm
Tasarım Kalıplarına GirişSingleton KalıbıFactory ve Abstract FactoryBuilder KalıbıObserver KalıbıStrategy Kalıbı2Bellek Yönetimi
Stack ve Heap Bellekİşaretçiler ve ReferanslarDinamik Bellek (new/delete)C++'ta Akıllı İşaretçilerC++'ta RAIIÖzet - Dinamik Dizi Yöneticisi5Kapsülleme
C++'da Erişim BelirleyicilerDerinlemesine Erişim BelirleyicilerBilgi GizlemeStruct vs Classİç İçe ve Dahili SınıflarÖzet - Öğrenci Kayıt Sistemi8Çok Biçimlilik
Derleme ve Çalışma Zamanı Çok BiçimliliğiFonksiyon Aşırı YüklemeSanal Fonksiyonlara Yeniden BakışSaf Sanal FonksiyonlarSoyut SınıflarC++'ta Arayüz TasarımıDynamic Casting ve RTTIÖzet - Şekil Hesaplayıcı3Yapıcılar ve Yıkıcılar
Varsayılan YapıcıParametreli YapıcıKopya YapıcıTaşıma YapıcısıYapıcı İlklendirme ListeleriTemsilci YapıcılarYıkıcılara Derinlemesine BakışÜç / Beş / Sıfır KuralıÖzet - String Sınıfı6Operatör Aşırı Yükleme
Operatör Aşırı Yüklemeye GirişAritmetik Operatör Aşırı YüklemeKarşılaştırma Operatörü Aşırı YüklemeStream OperatörleriAtama Operatörü Aşırı Yükleme[] ve () Operatör Aşırı YüklemeTip Dönüşüm OperatörleriÖzet - Matris Sınıfı9Şablonlar
Fonksiyon ŞablonlarıSınıf ŞablonlarıŞablon ÖzelleştirmeVariadic ŞablonlarSFINAE ve Type Traits TemelleriÖzet - Generic Konteyner12Modern C++ Özellikleri
Taşıma Semantiği ve R-değerleriMükemmel YönlendirmeDerinlemesine Lambda İfadeleristd::function ve std::bindconstexpr ve constevalYapılandırılmış Bağlamalaroptional, variant, any