Function Pointers ve Callbacks
C++'ta fonksiyonlar sadece çağrılmak için var değildir — aynı zamanda değer olarak da kullanılabilirler. Bir fonksiyonun adresini alabilir, bir değişkende saklayabilir, başka bir fonksiyona parametre olarak geçirebilirsin. Bu mekanizmaya function pointer (fonksiyon işaretçisi) diyoruz.
Function pointer'lar C'den miras kalan güçlü ama söz dizimi olarak biraz ürkütücü bir özellik. Bu derste bu söz dizimini adım adım çözeceğiz, callback pattern'ini öğreneceğiz ve modern C++'ın sunduğu std::function ve lambda alternatiflerini karşılaştıracağız.
Analoji: Function pointer'ları bir telefon rehberi gibi düşün. Rehberde kişinin adı (fonksiyon adı) ve telefon numarası (fonksiyonun bellek adresi) var. Numarayı bir kağıda yazıp (pointer'a atayıp) birine verebilirsin. O kişi kağıttaki numarayı arayarak (pointer üzerinden çağırarak) aynı kişiye ulaşır. Kağıdı istediğin kişiye verebilirsin — bu da callback pattern'inin temelidir.
Function Pointer Söz Dizimi
C++ dilindeki en kafa karıştırıcı söz dizimlerinden biri function pointer'lardır. Ama adım adım bakınca aslında mantıklı.
Temel Söz Dizimi
#include <iostream>
// Normal bir fonksiyon
int topla(int a, int b) {
return a + b;
}
int cikar(int a, int b) {
return a - b;
}
int main() {
// Function pointer tanımlama
// Dönüş_tipi (*pointer_adı)(parametre_tipleri)
int (*islem)(int, int);
// Fonksiyonun adresini ata
islem = topla; // & işareti opsiyonel: &topla da olur
// Pointer üzerinden çağır
int sonuc = islem(3, 4); // topla(3, 4) = 7
std::cout << "3 + 4 = " << sonuc << std::endl;
// Farklı bir fonksiyona yönlendir
islem = cikar;
sonuc = islem(10, 3); // cikar(10, 3) = 7
std::cout << "10 - 3 = " << sonuc << std::endl;
return 0;
}Söz dizimini parçalayalım: int (*islem)(int, int)
int→ dönüş tipi(*islem)→islemadında bir pointer (parantezler zorunlu!)(int, int)→ iki int parametre alan
Parantezler neden zorunlu? Çünkü int *islem(int, int) yazarsan, bu "int pointer döndüren bir fonksiyon" anlamına gelir — tamamen farklı bir şey!
Adım Adım Okuma Tekniği
Function pointer bildirimlerini okurken "saat yönünde spiral" (clockwise spiral) kuralını uygulayabilirsin, ama daha basit bir yol var:
Pointer adını bul:
(*islem)Sağına bak — parametreler:
(int, int)→ bu bir fonksiyonSoluna bak — dönüş tipi:
intSonuç: "
islem, iki int alan ve int döndüren bir fonksiyona pointer"
// Örnekler:
void (*callback)(void); // parametresiz, void döndüren
double (*hesapla)(double, double); // iki double alan, double döndüren
bool (*karsilastir)(const std::string&, const std::string&);
// iki string ref alan, bool döndürentypedef ve using ile Basitleştirme
Function pointer söz dizimi karmaşık. Bunu typedef veya (daha modern) using ile basitleştirebilirsin.
typedef Yöntemi (C tarzı)
#include <iostream>
// "IslemPtr" artık "iki int alan, int döndüren fonksiyon pointer'ı" demek
typedef int (*IslemPtr)(int, int);
int topla(int a, int b) { return a + b; }
int cikar(int a, int b) { return a - b; }
int carp(int a, int b) { return a * b; }
// Şimdi parametre olarak kullanmak çok temiz
int hesapla(IslemPtr islem, int x, int y) {
return islem(x, y);
}
int main() {
std::cout << hesapla(topla, 5, 3) << std::endl; // 8
std::cout << hesapla(cikar, 5, 3) << std::endl; // 2
std::cout << hesapla(carp, 5, 3) << std::endl; // 15
return 0;
}using Yöntemi (Modern C++ — Önerilen)
using söz dizimi daha okunabilir çünkü isim solda, tip sağda:
#include <iostream>
// using ile — çok daha okunabilir
using IslemPtr = int(*)(int, int);
using Comparator = bool(*)(int, int);
using Callback = void(*)(const std::string&);
int topla(int a, int b) { return a + b; }
int cikar(int a, int b) { return a - b; }
void islemYap(IslemPtr fn, int a, int b) {
std::cout << "Sonuç: " << fn(a, b) << std::endl;
}
int main() {
islemYap(topla, 10, 5); // Sonuç: 15
islemYap(cikar, 10, 5); // Sonuç: 5
return 0;
}using IslemPtr = int(*)(int, int); satırını şöyle oku: "IslemPtr, iki int alıp int döndüren bir fonksiyon pointer'ıdır." Çok daha net, değil mi?
Callback Pattern
Callback (geri çağırma), bir fonksiyonun davranışını dışarıdan parametrik olarak değiştirme yöntemidir. En klasik örnek: bir sıralama fonksiyonuna karşılaştırma kuralını dışarıdan vermek.
Sıralama ile Callback
#include <iostream>
#include <vector>
#include <string>
using Comparator = bool(*)(int, int);
// Genel amaçlı bubble sort — karşılaştırma mantığı dışarıdan gelir
void bubbleSort(std::vector<int>& arr, Comparator cmp) {
int n = arr.size();
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (cmp(arr[j], arr[j+1])) {
std::swap(arr[j], arr[j+1]);
}
}
}
}
// Farklı karşılaştırma fonksiyonları
bool kucuktenBuyuge(int a, int b) { return a > b; }
bool buyuktenKucuge(int a, int b) { return a < b; }
bool ciftlerOnce(int a, int b) {
if (a % 2 != b % 2) return a % 2 > b % 2;
return a > b;
}
int main() {
std::vector<int> v = {5, 2, 8, 1, 9, 3};
bubbleSort(v, kucuktenBuyuge);
std::cout << "Artan: ";
for (int x : v) std::cout << x << " "; // 1 2 3 5 8 9
std::cout << std::endl;
bubbleSort(v, buyuktenKucuge);
std::cout << "Azalan: ";
for (int x : v) std::cout << x << " "; // 9 8 5 3 2 1
std::cout << std::endl;
bubbleSort(v, ciftlerOnce);
std::cout << "Çiftler önce: ";
for (int x : v) std::cout << x << " "; // 2 8 1 3 5 9
std::cout << std::endl;
return 0;
}Burada bubbleSort fonksiyonu sıralamanın "nasıl" yapılacağını bilir, ama "neye göre" sıralanacağını bilmez. Bu bilgiyi callback fonksiyonu sağlar. Bu, Strategy pattern'inin en basit halidir.
Olay Tabanlı Callback
GUI programlama, oyun geliştirme ve asenkron programlamada callback'ler her yerdedir:
#include <iostream>
#include <string>
using OnClick = void(*)(const std::string&);
using OnHover = void(*)(const std::string&);
struct Button {
std::string label;
OnClick onClick;
OnHover onHover;
void click() {
if (onClick) onClick(label);
}
void hover() {
if (onHover) onHover(label);
}
};
void handleClick(const std::string& label) {
std::cout << "[CLICK] " << label << " butonuna tıklandı!" << std::endl;
}
void handleHover(const std::string& label) {
std::cout << "[HOVER] " << label << " üzerine gelindi." << std::endl;
}
void logClick(const std::string& label) {
std::cout << "[LOG] Tıklama kaydedildi: " << label << std::endl;
}
int main() {
Button btn1{"Kaydet", handleClick, handleHover};
Button btn2{"Logla", logClick, nullptr}; // hover yok
btn1.hover(); // [HOVER] Kaydet üzerine gelindi.
btn1.click(); // [CLICK] Kaydet butonuna tıklandı!
btn2.click(); // [LOG] Tıklama kaydedildi: Logla
return 0;
}⚠️ Dikkat: Function pointer
nullptrolabilir. Callback'i çağırmadan önce mutlaka null kontrolü yap (if (onClick) onClick(label);). Aksi takdirde nullptr'ı çağırma girişimi undefined behavior oluşturur ve büyük olasılıkla crash'e yol açar.
Function Pointer Dizileri — Dispatch Table
Function pointer'ların en güçlü kullanımlarından biri dispatch table (dağıtım tablosu) oluşturmaktır. Bu, uzun if-else veya switch-case zincirlerinin yerine kullanılabilir.
Hesap Makinesi Dispatch Table
#include <iostream>
double topla(double a, double b) { return a + b; }
double cikar(double a, double b) { return a - b; }
double carp(double a, double b) { return a * b; }
double bol(double a, double b) {
if (b == 0.0) {
std::cerr << "Sıfıra bölme hatası!" << std::endl;
return 0.0;
}
return a / b;
}
int main() {
using IslemFn = double(*)(double, double);
// Dispatch table — index ile fonksiyon seç
IslemFn islemler[] = {topla, cikar, carp, bol};
const char* isimler[] = {"+", "-", "*", "/"};
double a = 10.0, b = 3.0;
// switch-case yerine dispatch table!
for (int i = 0; i < 4; i++) {
double sonuc = islemler[i](a, b);
std::cout << a << " " << isimler[i] << " " << b
<< " = " << sonuc << std::endl;
}
// Kullanıcıdan seçim al
std::cout << "\nİşlem seçin (0:+, 1:-, 2:*, 3:/): ";
int secim;
std::cin >> secim;
if (secim >= 0 && secim < 4) {
std::cout << "Sonuç: " << islemler[secim](a, b) << std::endl;
}
return 0;
}Bu yaklaşımın avantajları:
Yeni işlem eklemek için sadece diziye bir eleman eklersin.
Çalışma zamanında (runtime) hangi fonksiyonun çağrılacağını seçebilirsin.
switch-casezincirinden çok daha temiz ve genişletilebilir.
Command Pattern ile Dispatch Table
Daha gelişmiş bir örnek — bir metin editörünün komut sistemi:
#include <iostream>
#include <string>
#include <map>
using Command = void(*)();
void cmdNew() { std::cout << "Yeni dosya oluşturuldu." << std::endl; }
void cmdOpen() { std::cout << "Dosya açılıyor..." << std::endl; }
void cmdSave() { std::cout << "Dosya kaydedildi." << std::endl; }
void cmdClose() { std::cout << "Dosya kapatıldı." << std::endl; }
void cmdHelp() {
std::cout << "Komutlar: new, open, save, close, help, quit" << std::endl;
}
int main() {
// String → fonksiyon eşlemesi
std::map<std::string, Command> commands;
commands["new"] = cmdNew;
commands["open"] = cmdOpen;
commands["save"] = cmdSave;
commands["close"] = cmdClose;
commands["help"] = cmdHelp;
std::string input;
std::cout << "Komut girin (quit ile çık): ";
while (std::getline(std::cin, input)) {
if (input == "quit") break;
auto it = commands.find(input);
if (it != commands.end()) {
it->second(); // Fonksiyonu çağır
} else {
std::cout << "Bilinmeyen komut: " << input << std::endl;
}
std::cout << "Komut girin: ";
}
return 0;
}std::map<std::string, Command> kullanarak string tabanlı bir dispatch table oluşturduk. Bu pattern, özellikle komut satırı uygulamaları, oyun scriptleri ve plugin sistemlerinde çok yaygındır.
Fonksiyonlara Fonksiyon Geçirme — Template Yaklaşımı
Function pointer'lar C tarzı bir çözüm. C++'ta template kullanarak daha genel ve daha performanslı bir yaklaşım da mümkün:
#include <iostream>
#include <vector>
#include <algorithm>
// Template ile — her türlü callable kabul eder
template<typename Func>
void herBirine(const std::vector<int>& v, Func f) {
for (int x : v) {
f(x);
}
}
void ekranaYaz(int x) {
std::cout << x << " ";
}
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// Function pointer geç
herBirine(v, ekranaYaz);
std::cout << std::endl;
// Lambda geç — aynı fonksiyon, farklı callable
herBirine(v, [](int x) {
std::cout << x * x << " ";
});
std::cout << std::endl;
return 0;
}Template yaklaşımının avantajı: derleyici tam olarak hangi fonksiyonun çağrılacağını bilir, bu yüzden inline edebilir. Function pointer ile bu mümkün değildir çünkü pointer'ın değeri runtime'da belli olur.
std::function — Esnek Alternatif
C++11 ile gelen std::function, function pointer'ların daha güçlü ve esnek halidir. <functional> başlık dosyasında tanımlıdır.
Nedir, Neden Var?
Function pointer'lar sadece sıradan fonksiyonlara (free functions) ve stateless lambda'lara (yakalama listesi boş olan) işaret edebilir. Ama gerçek dünyada şunlara da ihtiyaç duyarsın:
Capture'lı lambda'lar (dışarıdan değişken yakalayan)
Functor'lar (function call operator'ü olan nesneler)
Member fonksiyonlar
std::function tüm bunları tek bir tipte sarar:
#include <iostream>
#include <functional>
#include <string>
int topla(int a, int b) { return a + b; }
// Functor (fonksiyon nesnesi)
struct Carpici {
int katsayi;
int operator()(int x) const { return x * katsayi; }
};
int main() {
// Sıradan fonksiyon
std::function<int(int, int)> fn1 = topla;
std::cout << fn1(3, 4) << std::endl; // 7
// Lambda (capture'lı)
int offset = 100;
std::function<int(int)> fn2 = [offset](int x) { return x + offset; };
std::cout << fn2(42) << std::endl; // 142
// Functor
Carpici c{5};
std::function<int(int)> fn3 = c;
std::cout << fn3(10) << std::endl; // 50
// Boş kontrol — function pointer gibi nullptr olabilir
std::function<void()> fn4;
if (!fn4) {
std::cout << "fn4 boş!" << std::endl;
}
return 0;
}Söz dizimi: std::function<DönüşTipi(Parametre1, Parametre2, ...)>
Bu, function pointer'ın söz diziminden çok daha okunabilir:
Function pointer:
int (*fn)(int, int)std::function:
std::function<int(int, int)>
Event Handler Sistemi
std::function ile gerçek bir event handler sistemi kuralım:
#include <iostream>
#include <functional>
#include <vector>
#include <string>
#include <map>
class EventSystem {
// Her event adı için callback listesi
std::map<std::string, std::vector<std::function<void(const std::string&)>>> handlers_;
public:
// Event'e listener ekle
void on(const std::string& event,
std::function<void(const std::string&)> handler) {
handlers_[event].push_back(handler);
}
// Event'i tetikle
void emit(const std::string& event, const std::string& data = "") {
auto it = handlers_.find(event);
if (it != handlers_.end()) {
for (auto& handler : it->second) {
handler(data);
}
}
}
};
int main() {
EventSystem events;
// Farklı türde callback'ler ekle
// 1. Lambda
events.on("click", [](const std::string& data) {
std::cout << "Lambda handler: " << data << std::endl;
});
// 2. Capture'lı lambda
int clickCount = 0;
events.on("click", [&clickCount](const std::string& data) {
clickCount++;
std::cout << "Click #" << clickCount << ": " << data << std::endl;
});
// 3. Sıradan fonksiyon
events.on("error", [](const std::string& msg) {
std::cerr << "[ERROR] " << msg << std::endl;
});
// Event'leri tetikle
events.emit("click", "Kaydet butonu");
events.emit("click", "İptal butonu");
events.emit("error", "Bağlantı zaman aşımı");
return 0;
}Bu sistem std::function olmadan yazılamazdı, çünkü capture'lı lambda'lar function pointer'a dönüştürülemez. Function pointer sadece state'siz çağrılabilir nesnelerle çalışır.
Lambda vs Function Pointer vs std::function
Bu üçünü ne zaman kullanmalısın? İşte kapsamlı bir karşılaştırma:
Karşılaştırma Tablosu
| Özellik | Function Pointer | std::function | Lambda (template ile) |
|---|---|---|---|
| Söz dizimi | Karmaşık | Temiz | En temiz |
| Capture | ❌ Yok | ✅ Var | ✅ Var |
| Inline olabilir mi? | ❌ Hayır* | ❌ Hayır | ✅ Evet |
| Heap allocation | ❌ Yok | ⚠️ Olabilir | ❌ Yok |
| Boyut | 8 byte (64-bit) | ~32-64 byte | Capture'a göre değişir |
| nullptr olabilir mi? | ✅ Evet | ✅ Evet | ❌ Hayır |
| Runtime overhead | Minimal | Var (type erasure) | Yok |
| C API uyumlu | ✅ Evet | ❌ Hayır | ❌ Hayır |
| Depolanabilir | ✅ Evet | ✅ Evet | ⚠️ Template gerekir |
| Functor destekler | ❌ Hayır | ✅ Evet | ✅ Evet |
*\* Derleyici bazı özel durumlarda devirtualize edip inline edebilir, ama bu garantili değildir.*
Ne Zaman Hangisi?
Function pointer kullan:
C kütüphaneleriyle çalışıyorsan (C API callback'leri)
Capture'a ihtiyaç yoksa ve performans kritikse
Eski kodla (legacy code) uyumluluk gerekiyorsa
std::function kullan:
Callback'i bir sınıfın member'ında saklamanız gerekiyorsa
Capture'lı lambda, functor veya member function desteği lazımsa
Runtime'da farklı callable tipleri arasında geçiş yapılacaksa
Koleksiyon içinde (vector, map) callable saklanacaksa
Template + Lambda kullan:
Performans kritikse (inline + zero overhead)
Compile-time polimorfizm yeterliyse
STL algoritmaları gibi generic kodlarda
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
// 1. Function pointer — basit, C uyumlu
void cCallback(int value) {
std::cout << "C-style: " << value << std::endl;
}
// 2. Template — en performanslı, inline olabilir
template<typename Func>
void applyTemplate(int value, Func f) {
f(value);
}
// 3. std::function — en esnek, ama overhead var
void applyFunction(int value, std::function<void(int)> f) {
f(value);
}
int main() {
int multiplier = 3;
// Function pointer — capture OLMAZ
void (*fp)(int) = cCallback;
fp(42);
// Template — capture olur, inline olur
applyTemplate(42, [multiplier](int x) {
std::cout << "Template: " << x * multiplier << std::endl;
});
// std::function — capture olur, ama overhead var
applyFunction(42, [multiplier](int x) {
std::cout << "std::function: " << x * multiplier << std::endl;
});
return 0;
}Performans Farkları
Performans konusu genellikle abartılır, ama yoğun döngülerde fark edilebilir.
Function Pointer vs Indirect Call
Function pointer dolaylı bir çağrıdır (indirect call). CPU, hangi fonksiyonun çağrılacağını runtime'da çözer. Bu, branch prediction miss'lerine ve instruction cache miss'lerine yol açabilir. Ama pratikte modern CPU'lar indirect call'ları çok iyi tahmin eder.
std::function Overhead'i
std::function type erasure kullanır. Bu da demek oluyor ki:
Callable nesneyi sarmak için sanal fonksiyon benzeri bir mekanizma kullanır.
Küçük callable'lar "small buffer optimization" (SBO) ile yerinde tutulur.
Büyük callable'lar (çok capture'lı lambda'lar) heap'te ayrılır —
new/deleteoverhead'i.
#include <functional>
#include <iostream>
int main() {
// Boyut karşılaştırması
int (*fp)(int) = nullptr;
std::function<int(int)> fn;
std::cout << "Function pointer boyutu: " << sizeof(fp) << " byte" << std::endl;
std::cout << "std::function boyutu: " << sizeof(fn) << " byte" << std::endl;
// Tipik çıktı (64-bit sistemde):
// Function pointer boyutu: 8 byte
// std::function boyutu: 32 byte (veya 64, implementasyona göre)
return 0;
}💡 İpucu: Performans kaygısı varsa, önce ölç sonra optimize et. Çoğu uygulamada std::function'ın overhead'i ihmal edilebilir düzeydedir. Sadece çok sıkı döngülerde (milyon+ çağrı/saniye) fark hissedilebilir. O durumda bile template + lambda yaklaşımını tercih et, function pointer'a geri dönme.
Member Function Pointer'lar
Sınıf üye fonksiyonlarına da pointer alabilirsin, ama söz dizimi biraz daha karmaşık:
#include <iostream>
#include <string>
class Player {
public:
std::string name;
int health;
Player(const std::string& n, int h) : name(n), health(h) {}
void heal(int amount) {
health += amount;
std::cout << name << " iyileşti! Can: " << health << std::endl;
}
void damage(int amount) {
health -= amount;
std::cout << name << " hasar aldı! Can: " << health << std::endl;
}
};
int main() {
// Member function pointer söz dizimi
// Dönüş_tipi (Sınıf::*pointer_adı)(parametreler)
void (Player::*action)(int) = &Player::heal;
Player p("Ahmet", 100);
// Çağırma: nesne üzerinden .* operatörü
(p.*action)(20); // Ahmet iyileşti! Can: 120
// Farklı fonksiyona yönlendir
action = &Player::damage;
(p.*action)(30); // Ahmet hasar aldı! Can: 90
// Pointer üzerinden ->* operatörü
Player* ptr = &p;
action = &Player::heal;
(ptr->*action)(50); // Ahmet iyileşti! Can: 140
return 0;
}Gördüğün gibi .* ve ->* operatörleri kullanılıyor. Bu söz dizimi gerçekten çirkin. Pratikte member function pointer'lar yerine std::function veya std::bind (veya lambda) kullanmak çok daha temiz:
#include <functional>
#include <iostream>
class Calculator {
public:
double add(double a, double b) { return a + b; }
double multiply(double a, double b) { return a * b; }
};
int main() {
Calculator calc;
// Lambda ile sarma — en temiz yol
std::function<double(double, double)> op;
op = [&calc](double a, double b) { return calc.add(a, b); };
std::cout << op(3, 4) << std::endl; // 7
op = [&calc](double a, double b) { return calc.multiply(a, b); };
std::cout << op(3, 4) << std::endl; // 12
return 0;
}Pratik Örnek: Gelişmiş Hesap Makinesi
Öğrendiklerimizi birleştiren kapsamlı bir örnek yazalım:
#include <iostream>
#include <functional>
#include <map>
#include <string>
#include <cmath>
#include <stdexcept>
class Calculator {
std::map<std::string, std::function<double(double, double)>> binaryOps_;
std::map<std::string, std::function<double(double)>> unaryOps_;
public:
Calculator() {
// İkili işlemler
binaryOps_["+"] = [](double a, double b) { return a + b; };
binaryOps_["-"] = [](double a, double b) { return a - b; };
binaryOps_["*"] = [](double a, double b) { return a * b; };
binaryOps_["/"] = [](double a, double b) {
if (b == 0) throw std::runtime_error("Sıfıra bölme!");
return a / b;
};
binaryOps_["pow"] = [](double a, double b) { return std::pow(a, b); };
// Tekli işlemler
unaryOps_["sqrt"] = [](double x) { return std::sqrt(x); };
unaryOps_["abs"] = [](double x) { return std::abs(x); };
unaryOps_["neg"] = [](double x) { return -x; };
}
// Yeni ikili işlem ekle
void addBinaryOp(const std::string& name,
std::function<double(double, double)> op) {
binaryOps_[name] = op;
}
// Yeni tekli işlem ekle
void addUnaryOp(const std::string& name,
std::function<double(double)> op) {
unaryOps_[name] = op;
}
double compute(const std::string& op, double a, double b) {
auto it = binaryOps_.find(op);
if (it == binaryOps_.end()) {
throw std::runtime_error("Bilinmeyen işlem: " + op);
}
return it->second(a, b);
}
double compute(const std::string& op, double x) {
auto it = unaryOps_.find(op);
if (it == unaryOps_.end()) {
throw std::runtime_error("Bilinmeyen işlem: " + op);
}
return it->second(x);
}
};
int main() {
Calculator calc;
// Yerleşik işlemleri kullan
std::cout << "10 + 3 = " << calc.compute("+", 10, 3) << std::endl;
std::cout << "2 ^ 10 = " << calc.compute("pow", 2, 10) << std::endl;
std::cout << "sqrt(144) = " << calc.compute("sqrt", 144) << std::endl;
// Yeni işlem ekle — genişletilebilirlik!
calc.addBinaryOp("mod", [](double a, double b) {
return std::fmod(a, b);
});
std::cout << "17 mod 5 = " << calc.compute("mod", 17, 5) << std::endl;
return 0;
}Bu tasarım Open-Closed Principle'ı (Açık-Kapalı prensibi) güzelce uygular: hesap makinesi sınıfını değiştirmeden yeni işlemler ekleyebilirsin.
std::function ile Dikkat Edilmesi Gerekenler
Boş std::function Çağrısı
#include <functional>
int main() {
std::function<void()> fn; // Boş
// YANLIŞ — std::bad_function_call fırlatır!
// fn();
// DOĞRU — önce kontrol et
if (fn) {
fn();
}
return 0;
}Yaşam Süresi (Lifetime) Sorunları
#include <functional>
#include <iostream>
std::function<int()> tehlikeliFn() {
int x = 42;
// TEHLIKE — x fonksiyon bitince yok olur!
return [&x]() { return x; }; // Dangling reference!
}
std::function<int()> guvenilirFn() {
int x = 42;
// DOĞRU — x'i kopyala (capture by value)
return [x]() { return x; };
}
int main() {
auto bad = tehlikeliFn();
// bad() çağırmak UB! x artık yok.
auto good = guvenilirFn();
std::cout << good() << std::endl; // 42, güvenli
return 0;
}⚠️ Dikkat:
std::functioniçine lambda saklarken capture moduna çok dikkat et. Referans ile yakalanan değişkenler, lambda'yı oluşturan scope sona erince geçersiz olur. Callback'lerde value capture ([=]veya[x]) genellikle daha güvenlidir.
C API Callback'leri ile Çalışmak
Bazen C kütüphaneleri (SDL, GLFW, SQLite vb.) callback olarak function pointer bekler. Bu durumda std::function veya capture'lı lambda kullanamazsın — düz function pointer vermelisin:
#include <iostream>
// C-tarzı API simülasyonu
extern "C" {
typedef void (*EventCallback)(int eventCode, void* userData);
void registerCallback(EventCallback cb, void* userData) {
// Simülasyon: callback'i çağır
cb(42, userData);
cb(99, userData);
}
}
// C callback'ine uyumlu fonksiyon
void myHandler(int eventCode, void* userData) {
std::string* name = static_cast<std::string*>(userData);
std::cout << *name << " — Event: " << eventCode << std::endl;
}
int main() {
std::string appName = "MyApp";
// void* userData ile state taşınır
registerCallback(myHandler, &appName);
return 0;
}C API'lerinde state taşımak için void* userData parametresi kullanılır. Bu, type-safety'den yoksun ama C'nin sunduğu tek yoldur. Modern C++ kodunda bu pattern'e ihtiyaç duymazsın.
Özet
Function pointer bir fonksiyonun bellek adresini tutan değişkendir. Söz dizimi karmaşıktır:
int (*fp)(int, int).usingile basitleştir:using FnPtr = int(*)(int, int);Callback pattern, bir fonksiyonun davranışını dışarıdan özelleştirmek için function pointer/callable geçirme yöntemidir. Sıralama, event handling, strategy pattern gibi pek çok yerde kullanılır.
Dispatch table, function pointer dizisi veya map'i ile
switch-casezincirlerinin yerine kullanılabilir. Daha temiz, genişletilebilir ve bakımı kolaydır.std::function function pointer'ın modern, esnek alternatifidir. Lambda, functor, member function — her türlü callable'ı sarar. Ama type erasure nedeniyle hafif bir runtime overhead'i vardır.
Template + Lambda performans açısından en iyi seçenektir. Derleyici inline edebilir, zero overhead sunar. STL algoritmaları bu yaklaşımı kullanır.
Doğru aracı seç: C API uyumluluğu için function pointer, esneklik için
std::function, performans için template + lambda. Çoğu günlük koddastd::functionveya lambda ile template yeterlidir.
AI Asistan
Sorularını yanıtlamaya hazır