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çinde tanımlayabileceğiniz anonim işlevlerdir. STL algoritmalarıyla temel lambda ifadelerini görmüş olsanız da tam söz dizimlerini anlamak, değişkenleri yakalama ve bunlara nasıl erişileceğini denetleme konusunda güçlü olanakların kilidini açar.

Tam lambda söz dizimi şöyledir: [capture](parameters) mutable -> return_type { body }. Yakalama yan tümcesi, lambda'nın hangi dış değişkenlere ve nasıl 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; };     // Tümünü kopyala
    auto allByRef = [&]() { x++; y++; };           // Tümüne referans
    auto mixed = [x, &y]() { y += x; };            // İkisini karıştır
    
    byRef();
    std::cout << y << "\n";  // 25
}

Varsayılan olarak, değerle yakalanan değişkenler lambda içinde const olur. mutable anahtar sözcüğü 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, yeni değişkenler oluşturmanıza veya nesneleri lambda içine taşımanıza olanak tanıyan başlatma yakalamalarını ekledi:

auto ptr = std::make_unique<int>(42);
auto lambda = [p = std::move(ptr)]() {
    return *p;
};  // Sahiplik lambda'ya aktarıldı

Lambdalar, ayrı işlev nesneleri tanımlamadan davranışı bir parametre olarak geçirmeniz gerektiğinde (geri çağrılar, özel karşılaştırıcılar veya olay işleyicileri için) OOP'de özellikle kullanışlıdır.

challenge icon

Görev

Kolay

Farklı yakalama kipleriyle lambda ifadelerinin gücünü sergileyen bir olay işleyici sistemi oluşturalım. Geri çağrıları depolayıp çağıran basit bir olay dağıtıcısı oluşturacak ve lambdaların dış durumu çeşitli şekillerde nasıl yakalayabildiğini göstereceksin.

Kodunu üç dosyaya ayıracaksın:

  • EventDispatcher.h: Olay geri çağrılarını yöneten bir EventDispatcher sınıfı tanımla.

    Dağıtıcın, geri çağrıları std::vector türündeki bir std::function<void()> kullanarak depolamalı. Şu metotları ekle:

    • addCallback(std::function<void()> callback): listeye bir geri çağrı ekler
    • fireAll(): depolanan tüm geri çağrıları sırayla çağırır
    • clear(): tüm geri çağrıları kaldırır

    <functional> ve <vector> dosyalarını eklemen gerekecek.

  • EventDispatcher.cpp: Dağıtıcının metotlarını uygula. fireAll() metodu, tüm geri çağrılar üzerinde basitçe yineleme yapmalı ve her birini çağırmalıdır.
  • main.cpp: İki girdi oku:
    1. Bir temel sayı (tam sayı)
    2. Bir çarpan (tam sayı)

    Bir EventDispatcher oluştur ve üç geri çağrı ekleyerek farklı lambda yakalama tekniklerini göster:

    1. Temel sayıyı değer ile yakalayan ve şunu yazdıran bir lambda: Base value: [base]
    2. Çarpanı referans ile yakalayan, değerini 1 artıran ve ardından şunu yazdıran bir lambda: Multiplier after increment: [multiplier]
    3. Değer ile yakaladığı bir sayaç değişkenini (0 olarak başlatılmıştır) her çağrıldığında artıran ve şunu yazdıran mutable bir lambda: Call count: [counter]

    Tüm geri çağrıları ekledikten sonra, farklı yakalama kiplerinin birden çok çağrı boyunca nasıl davrandığını görmek için fireAll() metodunu iki kez çağır. İki fireAll() çağrısının arasında ayraç olarak --- yazdır.

    Son olarak, her iki turdan sonra referansla yakalamanın bu değişkeni nasıl etkilediğini göstermek için main içindeki çarpan değişkeninin son değerini yazdır: Final multiplier: [multiplier]

Örneğin, girdiler 10 ve 5 olduğunda:

Base value: 10
Multiplier after increment: 6
Call count: 1
---
Base value: 10
Multiplier after increment: 7
Call count: 1
Final multiplier: 7

Girdiler 42 ve 0 olduğunda:

Base value: 42
Multiplier after increment: 1
Call count: 1
---
Base value: 42
Multiplier after increment: 2
Call count: 1
Final multiplier: 2

Temel noktadaki davranışlara dikkat et: değer ile yakalama özgün temeli değiştirmeden korur, referans ile yakalama main içindeki gerçek çarpan değişkenini değiştirir (çağrılar boyunca birikerek ilerler) ve mutable lambda'nın sayacı her turda 1 değerini okur. Bunun nedeni, fireAll() metodunun geri çağrılarını değer ile yinelemesidir: her turda depolanan std::function nesnesinin yeni bir kopyası çağrılır; bu kopyanın yakalanan sayacı 0'dan başlar ve 1'e artırılır. Bu nedenle, depolanan geri çağrının kendi durumu hiçbir zaman ilerletilmez.

Kendin dene

#include <iostream>
#include "EventDispatcher.h"

using namespace std;

int main() {
    int base;
    int multiplier;
    cin >> base;
    cin >> multiplier;
    
    EventDispatcher dispatcher;
    
    // TODO: base'i BY VALUE yakalayan bir lambda ekle
    // Şunu yazdırmalı: "Base value: [base]"
    
    // TODO: multiplier'ı BY REFERENCE yakalayan bir lambda ekle
    // It should increment multiplier by 1, then print: "Multiplier after increment: [multiplier]"
    
    // TODO: Bir counter'ı (0 ile başlatılmış) by value yakalayan MUTABLE bir lambda ekle
    // counter'ı artırmalı ve şunu yazdırmalı: "Call count: [counter]"
    
    // TODO: Tüm callback'leri çağırmak için fireAll() çağır
    
    // TODO: Ayırıcı olarak "---" yazdır
    
    // TODO: fireAll()'ı tekrar çağır
    
    // TODO: Print the final multiplier value: "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

Kendi başına pratik yap: Online C++ derleyicisi