std::function ve std::bind
Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 86 / 104.
Lambdalar güçlü olsa da, bazen farklı türlerdeki çağrılabilir nesneleri tek tip bir şekilde saklamanız veya mevcut fonksiyonları gerekli bir imzaya uyacak şekilde uyarlamanız gerekir. <functional> başlığındaki std::function ve std::bind bu sorunları çözer.
std::function, belirli bir imzayla eşleşen herhangi bir çağrılabilir nesneyi -fonksiyonlar, lambdalar veya fonksiyon nesneleri- tutabilen, türü silinmiş (type-erased) bir sarmalayıcıdır:
#include <iostream>
#include <functional>
int add(int a, int b) { return a + b; }
int main() {
std::function<int(int, int)> operation;
operation = add; // Normal fonksiyon
std::cout << operation(3, 4) << "\n"; // 7
operation = [](int a, int b) { return a * b; }; // Lambda
std::cout << operation(3, 4) << "\n"; // 12
}std::bind mevcut bir fonksiyonun bazı argümanlarını sabitleyerek yeni bir çağrılabilir (callable) oluşturur. Değişken kalacak argümanları işaretlemek için std::placeholders::_1, _2 vb. kullanın:
#include <iostream>
#include <functional>
void greet(const std::string& greeting, const std::string& name) {
std::cout << greeting << ", " << name << "!\n";
}
int main() {
using namespace std::placeholders;
auto sayHello = std::bind(greet, "Hello", _1);
sayHello("Alice"); // Hello, Alice!
auto swapped = std::bind(greet, _2, _1);
swapped("Bob", "Hi"); // Hi, Bob!
}Bu araçlar, geri çağırmaları (callbacks) sınıf üyeleri olarak saklamak veya Strateji (Strategy) desenini uygulamak için OOP'de özellikle yararlıdır. Ancak, modern C++, daha iyi okunabilirlik ve performans için genellikle lambdaları std::bind yerine tercih eder - std::bind'ı esas olarak argümanları yeniden sıralamanız gerektiğinde veya eski kodlarla çalışırken kullanın.
Görev
Kolaystd::function ve std::bind'ın gücünü gösteren yapılandırılabilir bir hesap makinesi oluşturalım. Matematiksel işlemlerin çalışma zamanında saklanabildiği, değiştirilebildiği ve özelleştirilebildiği bir sistem oluşturacaksınız; bu araçların esnek geri çağırma (callback) yönetimini nasıl sağladığını göreceksiniz.
Kodunuzu üç dosya halinde düzenleyeceksiniz:
MathOperations.h: İşlem kütüphaneniz olarak hizmet edecek bağımsız matematiksel fonksiyonlar koleksiyonu tanımlayın.Şu fonksiyonları oluşturun:
add(int a, int b)— toplamı döndürürsubtract(int a, int b)— farkı döndürür (a - b)multiply(int a, int b)— çarpımı döndürürpower(int base, int exponent, int multiplier)—multiplier * (base ^ exponent)değerini döndürür. Üssü hesaplamak için basit bir döngü kullanın (üslerin negatif olmadığını varsayın).
Calculator.h: İşlemleri dinamik olarak saklamak ve yürütmek içinstd::functionkullanan birCalculatorsınıfı tanımlayın.Hesap makineniz şunlara sahip olmalıdır:
- Mevcut ikili işlemi saklamak için özel (private) bir
std::function<int(int, int)>üyesi setOperation(std::function<int(int, int)> op)— mevcut işlemi ayarlarcalculate(int a, int b)— saklanan işlemi yürütür ve sonucu döndürür
Gerekli başlık dosyalarını (
<functional>) ekleyin ve MathOperations başlık dosyanızı dahil ettiğinizden emin olun.- Mevcut ikili işlemi saklamak için özel (private) bir
main.cpp: Üç girdi okuyun:- İşlem adı:
add,subtract,multiplyveyasquare - Birinci sayı (tam sayı)
- İkinci sayı (tam sayı)
Bir
Calculatoroluşturun ve işlem adına göre yapılandırın:add,subtractvemultiplyiçin: ilgili fonksiyonu doğrudan hesap makinesine atayınsquareiçin:powerfonksiyonunun her zaman 2 üssünü ve 1 çarpanını kullanan özelleştirilmiş bir versiyonunu oluşturmak içinstd::bindkullanın. Bağlanan fonksiyon, yalnızca birincisinin taban olarak kullanıldığı iki argüman kabul etmelidir (ikinci argüman bir yer tutucu kullanılarak yoksayılabilir).
İşlemi ayarladıktan sonra, iki sayınızla
calculate()fonksiyonunu çağırın ve şunu yazdırın:Result: [value]Ardından, hesap makinesine
a + b + 100döndüren bir lambda atayarak, aynı girdilerle çalıştırarak ve şunu yazdırarak işlem değiştirme işlemini gösterin:With bonus: [value]- İşlem adı:
Örneğin, add, 10 ve 5 girdileriyle:
Result: 15
With bonus: 115multiply, 7 ve 3 girdileriyle:
Result: 21
With bonus: 110square, 4 ve 0 girdileriyle:
Result: 16
With bonus: 104Bu zorluk, std::function'ın farklı çağrılabilir türleri (normal fonksiyonlar, bağlanmış fonksiyonlar ve lambdalar) saklamak için nasıl tek tip bir yol sunduğunu, std::bind'ın ise mevcut fonksiyonları bazı argümanlarını sabitleyerek uyarlamanıza nasıl izin verdiğini gösterir.
Kopya kağıdı
<functional> başlığı, çağrılabilir nesnelerle esnek bir şekilde çalışmak için std::function ve std::bind sağlar.
std::function, belirli bir imzayla eşleşen herhangi bir çağrılabilir nesneyi saklayabilen, türü silinmiş (type-erased) bir sarmalayıcıdır:
#include <functional>
std::function<int(int, int)> operation;
operation = add; // Normal fonksiyon
operation = [](int a, int b) { return a * b; }; // Lambdastd::bind, mevcut bir fonksiyonun bazı argümanlarını sabitleyerek yeni bir çağrılabilir nesne oluşturur. Değişken argümanlar için std::placeholders::_1, _2 vb. kullanın:
#include <functional>
void greet(const std::string& greeting, const std::string& name) {
std::cout << greeting << ", " << name << "!\n";
}
using namespace std::placeholders;
auto sayHello = std::bind(greet, "Hello", _1); // İlk argümanı sabitle
sayHello("Alice"); // Hello, Alice!
auto swapped = std::bind(greet, _2, _1); // Argümanları yeniden sırala
swapped("Bob", "Hi"); // Hi, Bob!Bu araçlar, geri çağırmaları (callbacks) sınıf üyeleri olarak saklamak veya esnek geri çağırma sistemleri uygulamak için kullanışlıdır. Modern C++, daha iyi okunabilirlik için genellikle std::bind yerine lambdaları tercih eder.
Kendin dene
#include <iostream>
#include <string>
#include <functional>
#include "Calculator.h"
using namespace std;
int main() {
// Girdileri oku
string operation;
int num1, num2;
cin >> operation >> num1 >> num2;
// Bir Calculator örneği oluştur
Calculator calc;
// TODO: Hesap makinesini işlem adına göre yapılandırın
// "add", "subtract", "multiply" için: ilgili fonksiyonu doğrudan atayın
// "square" için: power fonksiyonunun özelleştirilmiş bir versiyonunu oluşturmak için std::bind kullanın
// her zaman 2 üssünü ve 1 çarpanını kullanan
// İpucu: Taban argümanı için std::placeholders::_1 kullanın
if (operation == "add") {
// Kodunuz buraya
} else if (operation == "subtract") {
// Kodunuz buraya
} else if (operation == "multiply") {
// Kodunuz buraya
} else if (operation == "square") {
// Kodunuz buraya - power fonksiyonu ile std::bind kullanın
}
// TODO: calculate() fonksiyonunu çağırın ve sonucu yazdırın
// Format: "Result: [value]"
// TODO: İşlemlerin yer değiştirmesini gösterin
// Hesap makinesine a + b + 100 döndüren bir lambda atayın
// calculate() fonksiyonunu tekrar çağırın ve sonucu yazdırın
// Format: "With bonus: [value]"
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