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çi (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öz dizimi şöyledir: [capture](parameters) mutable -> return_type { body }. Yakalama ifadesi (capture clause), lambdanın hangi dış değişkenlere hangi yöntemle erişebileceğ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; }; // Her ikisini de karıştır
byRef();
std::cout << y << "\n"; // 25
}Varsayılan olarak, değer ile yakalanan 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, lambda içine yeni değişkenler oluşturmanıza veya nesneleri 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, ayrı fonksiyon nesneleri tanımlamadan - geri çağırmalar (callbacks), özel karşılaştırıcılar veya olay işleyiciler için - davranışı bir parametre olarak geçirmek istediğinizde OOP'de özellikle yararlıdır.
Görev
KolayFarklı yakalama (capture) 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 durumu ç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önmeli 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ğerine göre (by value) yakalayan ve şunu yazdıran bir lambda:
Base value: [base] - Çarpanı referansa göre (by reference) yakalayan, değerini 1 artıran ve ardından şunu yazdıran bir lambda:
Multiplier after increment: [multiplier] - Bir sayaç değişkenini (0 olarak başlatılmış) değerine göre yakalayan, her çağrıldığında 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ğerine göre yakalama orijinal tabanı değiştirmez, referansa göre 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'e sıfırlanır çünkü her fireAll() aynı saklanan lambda nesnesini kendi kalıcı dahili kopyasıyla çağırır—ancak std::function kopyalarını sakladığımız için, uygulamanızda mutable durumun nasıl davrandığını gözlemleyin.
Kopya kağıdı
Lambda ifadeleri, satır içinde tanımlanabilen anonim fonksiyonlardır. Tam sözdizimi şöyledir:
[capture](parameters) mutable -> return_type { body }Yakalama İfadeleri (Capture Clauses)
Yakalama ifadesi, lambdanın hangi dış değişkenlere nasıl erişebileceğini belirler:
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; }; // Tüm değişkenleri kopyala
auto allByRef = [&]() { x++; y++; }; // Tüm değişkenlere referans ver
auto mixed = [x, &y]() { y += x; }; // Her iki modu karıştırDeğiştirilebilir (Mutable) Lambdalar
Varsayılan olarak, değer ile yakalanan (captured-by-value) değişkenler lambda içinde const durumundadır. Kopyaları değiştirmek için mutable kullanın:
int counter = 0;
auto increment = [counter]() mutable {
return ++counter; // Lambdanın kopyasını değiştirir
};
std::cout << increment() << "\n"; // 1
std::cout << increment() << "\n"; // 2
std::cout << counter << "\n"; // 0 - orijinal değişmediBaşlatma Yakalamaları (Init Captures - C++14)
Lambda içinde yeni değişkenler oluşturun veya nesneleri lambdanın içine taşıyın:
auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
return *p;
}; // Sahiplik lambdaya devredildiLambdaların std::function ile Kullanımı
Geri çağırma (callback) ve olay işleyicileri (event handler) için lambdaları std::function içinde saklayın:
#include <functional>
#include <vector>
std::vector<std::function<void()>> callbacks;
callbacks.push_back([x]() { /* x'i kullan */ });
callbacks.push_back([&y]() { /* y'yi değiştir */ });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 (BY VALUE) yakalayan bir lambda ekleyin
// Şunu yazdırmalı: "Base value: [base]"
// TODO: multiplier değişkenini REFERANS İLE (BY REFERENCE) 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) yürütmek 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