QT SIGNAL SLOT NEDİR?
Bir buton tıklandığında ne olacağını söylemek için fonksiyon pointer'ı mı geçirmek, observer arayüzü mü implement etmek, yoksa tek satırla connect yazmak mı? Aynı problemi çözen üç farklı yaklaşım var ve seçim, kodun güvenliği ile bakım maliyetini doğrudan etkiliyor. Qt'nin signal/slot mekanizması bu üç dünyaya da alternatif sunar; ama nasıl ve neden farklı olduğunu anlamak için klasik yaklaşımların eksiklerine bakmak gerekir.
Signal/Slot Mekanizmasının Temel Mantığı
Qt'de signal, bir nesnenin "bir şey oldu" diye yaydığı bildirimdir. Slot ise bu bildirime cevap veren fonksiyondur. İki nesneyi birbirine bağlamak için QObject::connect çağrısı kullanılır ve bağlantı koptuğunda — örneğin nesnelerden biri yok edildiğinde — Qt bağlantıyı otomatik temizler.
Klasik örnek üzerinden bakalım:
connect(button, &QPushButton::clicked,
this, &MyWindow::onButtonClicked);Burada button nesnesi clicked sinyalini yaydığında, MyWindow sınıfının onButtonClicked slot'u tetiklenir. Sinyali yayan nesnenin slot'u kimin uyguladığını bilmesine gerek yoktur — bu da loose coupling sağlar.
Konuya ilişkin proje sayfasını ek bir başvuru kaynağı olarak değerlendirilebilir.
Callback Yaklaşımı ile Karşılaştırma
Klasik C callback'leri fonksiyon pointer'ı geçirmeye dayanır. Yazması kısa olsa da pek çok zayıf nokta taşır:
- Tip güvenliği zayıftır — yanlış imzalı fonksiyon geçilirse hata çoğu zaman çalışma zamanında patlar.
- Callback'in çağıracağı nesne (context) ayrı bir
void*parametresiyle taşınır ve cast hataları yaygındır. - Birden fazla dinleyici eklemek isterseniz, kendi liste yapınızı kurmanız gerekir.
- Nesne yok edildiğinde callback temizliği elle yapılmazsa dangling pointer riski vardır.
Signal/slot ise derleme zamanında imza kontrolü yapar (yeni syntax ile), aynı sinyale birden çok slot bağlamayı doğal olarak destekler ve QObject yok edildiğinde bağlantıyı otomatik düşürür. Yani void* taşımanız ve manuel temizlik yazmanız gerekmez.
Observer Pattern ile Karşılaştırma
Observer pattern, dinleyicilerin ortak bir arayüz (örneğin IObserver) implement etmesini ve subject sınıfının bir liste tutmasını gerektirir. Her yeni olay tipi için arayüze yeni metot eklemek ya da yeni bir arayüz açmak gerekir. Bu, küçük projelerde temiz dursa da çok sayıda olay tipi olduğunda hızla şişen bir arayüz hiyerarşisi yaratır.
Signal/slot'un farkı şudur: her sinyal kendi imzasını taşır, bir arayüz tanımlamak zorunda değilsiniz ve dinleyiciler birbirinden tamamen habersizdir. Aynı slot'a lambda, üye fonksiyon ya da serbest fonksiyon bağlayabilirsiniz:
connect(timer, &QTimer::timeout, [&]() {
counter++;
label->setText(QString::number(counter));
});Bu esneklik observer pattern'da doğrudan yoktur; her seferinde bir adaptör sınıfı yazmanız gerekir.
Type Safety: Eski ve Yeni Syntax
Qt'nin iki connect syntax'ı vardır. Eskisi string tabanlıdır:
connect(button, SIGNAL(clicked()), this, SLOT(onClicked()));Burada imza string olarak tutulur ve hata varsa çalışma zamanında konsola uyarı düşer. Yeni syntax fonksiyon pointer'ı tabanlıdır:
connect(button, &QPushButton::clicked, this, &MyWindow::onClicked);Bu form derleme zamanında imza uyuşmazlığını yakalar, lambda'ları kabul eder ve IDE'lerde "go to definition" çalışır. Yeni projelerde tercih edilen budur. Qt'nin temel kavramlarını Qt C++ eğitimi içeriklerinden öğrenmek için faydalanabilirsiniz.
Thread Safety ve Connection Type
Callback ve observer pattern'da farklı thread'lerden çağrı yapılırsa senkronizasyonu siz yönetmek zorundasınız. Signal/slot bu noktada beş bağlantı tipi sunar:
- Qt::DirectConnection: Slot, sinyal yayılan thread'de hemen çalışır.
- Qt::QueuedConnection: Slot, alıcı nesnenin thread'inin event loop'una atılır.
- Qt::BlockingQueuedConnection: Queue'ya atar, sinyal yayan thread cevap dönene kadar bekler.
- Qt::AutoConnection: Sinyal ve slot aynı thread'deyse direct, değilse queued davranır (varsayılan).
- Qt::UniqueConnection: Aynı bağlantı varsa tekrar eklemeyi engelleyen flag.
Yani bir worker thread'den UI thread'e veri yollamak için elle mutex tutmanıza gerek yoktur — QueuedConnection üzerinden gönderilen argümanlar otomatik kopyalanır ve hedef thread'in event loop'unda işlenir.
Performans ve Maliyet
Signal/slot bedavaya gelmez. Bir doğrudan fonksiyon çağrısına göre yaklaşık 10 kat daha yavaştır; çünkü Qt meta-object sistemi argümanları paketler, slot'u arar ve çağırır. Ancak bu, milyonlarca çağrı yapmadığınız sürece pratikte hissedilmez. UI olayları, sensör verisi, ağ paketi gibi tipik kullanımlarda fark ölçülemez seviyededir.
Karşılaştırma tablosu özet olarak şöyle özetlenebilir:
- Callback: En hızlı, en az tip güvenliği, manuel yaşam döngüsü yönetimi.
- Observer: Tip güvenli ama arayüz tasarımı şişer, thread desteği yok.
- Signal/Slot: Tip güvenli, thread-aware, otomatik temizlik; meta-object overhead'i var.

MOC ve Meta-Object Sistemi
Signal/slot'un nasıl çalıştığını anlamak için Meta-Object Compiler'a (MOC) bakmak gerekir. Q_OBJECT makrosu bulunan sınıflar MOC tarafından işlenir; sinyaller için kod üretilir, slot'lar meta-object tablosuna eklenir. Bu sayede Qt çalışma zamanında "bu sinyal hangi slot'lara bağlı" sorusunu cevaplayabilir ve QueuedConnection için argümanları serileştirebilir.
Yani signal/slot dilin parçası değil, bir kod üreticisi tarafından desteklenen bir mekanizmadır. C++20 sonrasında bazı alternatif kütüphaneler (Boost.Signals2, sigslot) MOC olmadan benzer işlevsellik sunsa da, Qt'nin thread bağlantısı ve introspection desteği ile aynı paketi vermezler.
Hangi Durumda Hangisi?
Pratik bir tercih rehberi:
- Saf C API'leri için ve performansın mikrosaniye düzeyinde önemli olduğu yerlerde callback.
- Qt dışı, basit observer ilişkileri için (örneğin oyun motoru içi event bus) observer pattern.
- Qt uygulaması yazıyorsanız ya da thread'ler arası tip güvenli iletişim istiyorsanız signal/slot.
Daha derinine inmek ve QObject, MOC, parent/child ilişkisi gibi yapıları örnek projelerle pekiştirmek için Qt C++ eğitim serisini inceleyebilirsiniz.
Sonuç olarak signal/slot, callback ve observer pattern'ın iyi yanlarını birleştiren bir ara katmandır: tip güvenliği derleme zamanında, yaşam döngüsü otomatik, thread geçişi yapılandırılabilir. Küçük bir runtime maliyetine karşılık çok daha az boilerplate kod ve çok daha az dangling pointer hatası alırsınız — bu da pratikte aradığınız değiş tokuş olduğunda doğru araç olur.



