New Signal Slot Syntax/tr

From Qt Wiki
Jump to navigation Jump to search

Yeni Signal Slot Sözdizimi (Qt5)

Aşağıdaki bağlantılar yeni signal-slot sözdizimi henüz geliştirilirken kaleme alınmıştır. Yeni signal-slot sözdizimi Qt5 ile birlikte kullanıma sunulmuştur.

Not: Eski string tabanlı sözdizimi geçerliliğini korumaktadır.

Durum

  • qtbase/master dalı ile birleştirilmiştir

Qt5'te Signal-Slot Bağlama

Qt5 signale bağlanmak için birden çok yöntem sunar.

Eski sözdizimi

Qt5, QObject veya QObject'ten türetilmiş (QWidget dahil) herhangi bir sınıfta tanımlanmış signal ve slotların birbirine bağlanması için eski string tabanlı sözdizimi kullanımına hala destek vermektedir.

connect(sender, SIGNAL (valueChanged(QString,QString)),
 receiver, SLOT (updateValue(QString)) );

Yeni: QObject üyesine bağlanma

İşte Qt5 ile sunulan iki QObject'in birbirine bağlanmasını sağlayan yeni sözdizimi:

connect(sender, &Sender::valueChanged,
 receiver, &Receiver::updateValue );

artıları

  • Signal ve slotların varlığının ve tür uyuşmasının veya Q_OBJECT makrosunun eksik olup olmadığının kontrolünün derleme zamanında yapılması
  • Argümanlar, typedef tanımlamaları veya farklı isim uzayı tanımlayıcıları olabilir.
  • Örtük tür dönüşümü (implicit conversion) olduğu durumlarda otomatik olarak dönüşümün (casting) gerçekleştirilmesi (örn. QString türünün QVariant türüne dönüştürülmesi)
  • QObject'in herhangi bir üye fonksiyonuna bağlanabilmesi, sadece slot fonksiyonlara bağlanmaması

eksileri

  • Daha karmaşık sözdizimi (nesnenizin türünü belirtmek zorundasınız)
  • Overload durumunda hangi fonksiyonun kullanılacağını belirtmek için çok daha karmaşık sözdiziminin gerekli olması (bakınız)
  • Slot fonksiyondaki varsayılan argümanlar artık desteklenmiyor.

Yeni: basit bir fonksiyona bağlanma

Yeni sözdizimi sadece QObject'ten türetilen nesnelerin fonksiyonlarına değil sade fonksiyonlara da bağlanılabilir:

connect(sender, &Sender::valueChanged, someFunction);

artıları

  • tr1::bind deyimi ile kullanılabilmesi:
connect(sender, &Sender::valueChanged,
  tr1::bind(receiver, &Receiver::updateValue, "senderValue", tr1::placeholder::_1));
  • c++11 lambda deyimleri ile kullanılabilmesi:
connect(sender, &Sender::valueChanged, [=](const QString &newValue) {
  receiver->updateValue("senderValue", newValue);
});

eksileri

  • 'Alıcı' yok edildiğinde (destroy) otomatik olarak signal slot bağlantısı koparılmaz çünkü 'alıcı' sadece QObject'ten türetilmeyen bir fonksiyon nesnesidir (functor). Buna rağmen, 5.2 sürümünden itibaren "bağlam nesnesi" (context object) ekleyen yeni bir overload sunulmuştur. Bağlam nesnesi yok edildiğinde (destroy) bağlantı kopar (bağlam nesnesi aynı zamanda thread benzeşimi -thread affinity- için de kullanılır: bağlam olarak kullanılan nesnenin olay döngüsünün (event loop) bulunduğu threadde çağrılacaktır).

Bağlantıyı koparma

Qt5 bağlantıların sonlandırılması konusunda da birtakım değişiklikler getirmiştir.

Eski yöntem

Aşağıdaki durumlar haricinde eski string tabanlı sözdizimini (SIGNAL, SLOT) kullanarak signal ve slot arasındaki bağlantıyı koparabilirsiniz:

  • eski sözdizimi ile bağladıysanız veya
  • bir signalden tüm slotları asterisk (*) karakterini kullanarak koparmak istediğinizde

Fonksiyon göstericileri (function pointers) simetrik olduğunda

disconnect(sender, &Sender::valueChanged,
 receiver, &Receiver::updateValue );

Sadece fonksiyon göstericileri ile simetrik çağrı kullanarak bağladığınızda çalışır. Statik fonksiyonlar, fonksiyon nesneleri (functor) ve lambda fonksiyonları ile çalışmaz.

QMetaObject::Connection kullanma

QMetaObject::Connection m_connection;
//…
m_connection = QObject::connect();
//…
QObject::disconnect(m_connection);

Lambda fonksiyonlar ve fonksiyon nesneleri (functor) dahil tüm durumlarda çalışır.


Asenkronluk artık daha kolay

C11 ile kodu satıriçinde (inline) tutmak artık mümkündür.


void doYourStuff(const QByteArray &page)
{
 QTcpSocket *socket = new QTcpSocket;
 socket->connectToHost("qt.io", 80);
 QObject::connect(socket, &QTcpSocket::connected, [socket, page] () {
 socket->write(QByteArray("GET " + page + ""));
 });
 QObject::connect(socket, &QTcpSocket::readyRead, [socket] () {
 qDebug()<< "GOT DATA "<< socket->readAll();
 });
 QObject::connect(socket, &QTcpSocket::disconnected, [socket] () {
 qDebug()<< "DISCONNECTED ";
 socket->deleteLater();
 });

 QObject::connect(socket, static_cast<void (QTcpSocket::*)(QAbstractSocket::SocketError)>
(&QAbstractSocket::error), [socket](QAbstractSocket::SocketError) {
 qDebug()<< "ERROR " << socket->errorString();
 socket->deleteLater();
 });
}

İşte bu da kodu ait olduğu yerde tutarak, olay döngüsüne (event loop) tekrardan girmeyen bir QDialog örneğidir:

void Doc::saveDocument() {
 QFileDialog *dlg = new QFileDialog();
 dlg->open();
 QObject::connect(dlg, &QDialog::finished, [dlg, this](int result) {
 if (result) {
 QFile file(dlg->selectedFiles().first());
 // …
 }
 dlg->deleteLater();
 });

}

QHttpServer sınıfı kullanan başka bir örnek: http://pastebin.com/pfbTMqUm

Hata raporu

GCC ile test edilmiştir.

Neyse ki Qt Creator gibi IDE'ler fonksiyon adlandırmayı kolaylaştırmaktadır.

Q_OBJECT makrosunu unutun

#include <QtCore>
class Goo : public QObject {
 Goo() {
 connect(this, &Goo::someSignal, this, &QObject::deleteLater);
 }
signals:
 void someSignal();
};
qobject.h: In member function 'void QObject::qt_check_for_QOBJECT_macro(const T&&) const [with T = Goo]':
qobject.h:535:9: instantiated from 'static typename QtPrivate::QEnableIf<((int)
(QtPrivate::FunctionPointer<Func>::ArgumentCount) >= (int)
(QtPrivate::FunctionPointer<Func2>::ArgumentCount)), void*>::Type QObject::connect(const typename
QtPrivate::FunctionPointer<Func>::Object*, Func1, const typename QtPrivate::FunctionPointer<Func2>::Object*,
Func2, Qt::ConnectionType) [with Func1 = void (Goo::*)(), Func2 = void (QObject::*)(), typename
QtPrivate::QEnableIf<((int)(QtPrivate::FunctionPointer<Func>::ArgumentCount) >= (int)
(QtPrivate::FunctionPointer<Func2>::ArgumentCount)), void*>::Type = void*, typename 
QtPrivate::FunctionPointer<Func>::Object = Goo, typename QtPrivate::FunctionPointer<Func2>::Object = QObject]'
main.cc:4:68: instantiated from here
qobject.h:353:5: error: void value not ignored as it ought to be
make: '''* [main.o] Error 1

Tür uyuşmazlığı

#include <QtCore>
class Goo : public QObject {
Q_OBJECT
public:
 Goo() {
 connect(this, &Goo::someSignal, this, &Goo::someSlot1); //hata
 connect(this, &Goo::someSignal, this, &Goo::someSlot2); //çalışır
 }
signals:
 void someSignal(QString);
public:
 void someSlot1(int);
 void someSlot2(QVariant);
};
qobject.h: In static member function 'static typename QtPrivate::QEnableIf<((int)
(QtPrivate::FunctionPointer<Func>::ArgumentCount) >= (int)
(QtPrivate::FunctionPointer<Func2>::ArgumentCount)), void*>::Type QObject::connect(const typename
QtPrivate::FunctionPointer<Func>::Object*, Func1, const typename QtPrivate::FunctionPointer<Func2>::Object*,
Func2, Qt::ConnectionType) [with Func1 = void (Goo::*)(QString), Func2 = void (Goo::*)(int), typename
QtPrivate::QEnableIf<((int)(QtPrivate::FunctionPointer<Func>::ArgumentCount) >= (int)
(QtPrivate::FunctionPointer<Func2>::ArgumentCount)), void*>::Type = void*, typename 
QtPrivate::FunctionPointer<Func>::Object = Goo, typename QtPrivate::FunctionPointer<Func2>::Object = Goo]':
main.cc:6:62: instantiated from here
qobject.h:538:163: error: no type named 'IncompatibleSignalSlotArguments' in 'struct 
QtPrivate::CheckCompatibleArguments<QtPrivate::List<QString, void>, QtPrivate::List<int, void>, true>'
qobject.h: In static member function 'static void QtPrivate::FunctionPointer<Ret (Obj::*)(Arg1)>::call
(QtPrivate::FunctionPointer<Ret (Obj::*)(Arg1)>::Function, Obj*, void*) [with Args = QtPrivate::List<QString, void>,
Obj = Goo, Ret = void, Arg1 = int, QtPrivate::FunctionPointer<Ret (Obj::*)(Arg1)>::Function = void (Goo::*)(int)]':
qobject.h:501:13: instantiated from 'void QObject::QSlotObject<Func, Args>::call(QObject*, void**) [with Func =
void (Goo::*)(int), Args = QtPrivate::List<QString, void>, QObject = QObject]'
main.cc:14:2: instantiated from here
qobject.h:109:13: error: cannot convert 'QtPrivate::RemoveRef<QString>::Type' to 'int' in argument passing
make: *** [main.o] Error 1

Açık Sorular

Slot fonksiyondaki varsayılan argümanlar

Aşağıdaki gibi bir kodunuz varsa:

class A : public QObject { Q_OBJECT
 public slots:
 void someSlot(int foo = 0);
};

Eski yöntem bu slotu argümanı olmayan signale bağlamanıza imkan tanırdı. Fakat template kodda varsayılan argüman olup olmadığını bilemem. Bu yüzden bu özellik iptal edildi.

Slotta signalden daha fazla argüman olduğunda eski yönteme geri dönen bir özellik gerçeklemiştik. Fakat bu özellikle hafif tutarsızdı çünkü eski yöntemde tür kontrolü ve tür dönüşümü yapılmıyordu. Bu özellik anadal ile birleştirilen yamadan çıkartıldı.

Overload

Asenkronluk artık daha kolay bölümündeki örnekte gördüğünüz üzere, QAbstractSocket::error signaline bağlanmak göze hoş gelmiyordu çünkü bu signal overloada sahipti ve overload edilmiş fonksiyonda açıkça argüman türlerinin belirtilmesi (explicit casting) gerekiyordu. Örneğin daha önceden signal-slot bağlantısını şu şekilde yapmıştık:

connect(mySpinBox, SIGNAL(valueChanged(int)), mySlider, SLOT(setValue(int));

ve bu kolaylıkla yeni sözdizimine çevrilemez:

connect(mySpinBox, &QSpinBox::valueChanged, mySlider, &QSlider::setValue);

...çünkü QSpinBox valueChanged() adında iki farklı argümana sahip bir signaldir. Yani yeni sözdizimi ile yapılan bağlantı şöyle olmalıdır:

connect(mySpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), mySlider, &QSlider::setValue);

Bazı makrolar bu sözdizimi kalabalığından kurtulmanıza yardımcı olabilir (c11 ile gelen typeof eklentisi)

Herhalde en iyi çözüm signal veya slotları overload etmemeniz olacaktır …

… fakat biz geçmişteki Qt sürümlerinde fonksiyonun adresinin bir kullanımı olmadığı için overload fonksiyonlar gerçeklemiştik. Şimdi kaynak kodu uyumluluğunu bozmadan bunu gerçeklemek imkansız olacağından ne yazık ki kullanmanız gereken durumlar olacaktır.

Bağlantıyı koparma

QMetaObject::Connection sınıfının disconnect() fonksiyonu olması gerekir mi?

Bir diğer problemde signal slot bağlantısı yaparken closure kullanıldığında closure içindeki bazı nesnelerin bağlantılarının otomatik olarak kopmamasıydı. Ya biri bağlantı koparılırken kullanılması için nesneleri listeye ekleyecekti (birinci yöntem) veya QMetaObject::Connection::require gibi yeni bir yöntem (ikinci yöntem) tanımlanacaktı:

auto c = connect(sender, &Sender::valueChanged, [=](const QString &newValue) {
 receiver->updateValue("senderValue", newValue);
 } , QList<QObject> { receiver } ); // birinci yöntem
c.require(receiver); // ikinci yöntem

İkinci yöntem hala geçerli mi? QMetaObject::Connection::require() gibisi yok.

Geri Çağrımlar (Callbacks)

QHostInfo::lookupHost, QTimer::singleShot veya QFileDialog::open fonksiyonlar QObject alıcısı ve char* slotu argüman olarak alır. Bu yeni yöntemde çalışmayacaktır. Eğer biri C++ yolu ile callback çalıştırmak istiyorsa std::function (veya tr1) kullanmalıdır. Fakat biz ABI (Application Binary Interface) arayüzünde STL türlerini kullanamayız. Bunu sağlamak için QFunction std::function kopyalayabilir. Bu durum QObject bağlantıları için geçerli değildir.

Tarihçe