Menu
Coddy logo textTech

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şmedi

C++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.

challenge icon

Görev

Kolay

Farklı 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 bir EventDispatcher sınıfı tanımlayın.

    Dağıtıcınız, geri çağırmaları std::function<void()> türünde bir std::vector kullanarak saklamalıdır. Şu metotları ekleyin:

    • addCallback(std::function<void()> callback) — listeye bir geri çağırma ekler
    • fireAll() — saklanan tüm geri çağırmaları sırayla çağırır
    • clear() — 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:
    1. Bir taban sayı (tam sayı)
    2. Bir çarpan (tam sayı)

    Bir EventDispatcher oluşturun ve üç geri çağırma ekleyerek farklı lambda yakalama tekniklerini gösterin:

    1. Taban sayıyı değerine göre (by value) yakalayan ve şunu yazdıran bir lambda: Base value: [base]
    2. Çarpanı referansa göre (by reference) yakalayan, değerini 1 artıran ve ardından şunu yazdıran bir lambda: Multiplier after increment: [multiplier]
    3. 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. İki fireAll() ç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: 7

42 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: 2

Temel 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ır

Değ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şmedi

Baş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 devredildi

Lambdaları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;
}
quiz iconKendini test et

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