Qt-Anwendungen nutzen das Signal-Slot-Konzept, um Events zu verarbeiten. Programmierer definieren diese Signale und Slots als Methoden: Signal-Methoden repräsentieren dabei die Events, einer oder mehrere Slots enthalten die Methoden, die das Qt-Programm aufruft, wenn sich ein Event ereignet. Die Methode »QObject::connect()« sorgt für die richtige Zuordnung: Sie verbindet ein Signal mit einem Slot (Abbildung 1).
Abbildung 1: Qt-Anwendungen verwenden Signale (links) und Slots (rechts) für die Event-Verarbeitung.
Das Herstellen der Verbindung und die Aufrufe beim Auslösen des Ereignisses finden zur Laufzeit statt: Erst dadurch sind viele elegante Lösungen implementierbar, zum Beispiel dynamisch erzeugte GUIs. Doch wenn alles zur Laufzeit passiert, stellt sich die Frage, wie Entwickler etwa Tippfehler in den Namen der Signal- oder Slot-Methoden erkennen können. Zwar ist auch dies zur Laufzeit möglich, aber hier liegt die Schwierigkeit beim Debuggen. Qt schreibt im Debug-Modus aussagekräftige Warnungen auf die Konsole, aber in der Praxis steht häufig kein Konsolenfenster zur Verfügung oder es ist bereits mit anderen Meldungen überladen.
Dreizehn Regeln zur Fehlervermeidung
Um nicht in immer wiederkehrende Fallen zu stapfen, helfen 13 einfache Regeln dabei, Fehler zu vermeiden. Eine Übersicht gibt Tabelle 1.
Wenn der Entwickler eine neue Verbindung einrichtet, prüft er, ob die Parametertypen des Signals zu denen des zugeordneten Slots passen. Zulässig sind vollständig übereinstimmende Typen, aber auch der Fall, in dem die Slot-Methode weniger Typen als das Signal definiert, ist gültig. Einzig mehr oder gänzlich unterschiedliche Parameter sind hier nicht erlaubt (Regel 1).
In den »SIGNAL«- und »SLOT«-Makros der Qt-»connect()«-Anweisung darf der Programmierer ausschließlich Typen, aber keine Variablen definieren und sollte darum prüfen, ob sich versehentlich ein Variablenname eingeschlichen hat (Regel 2):
connect(this, SIGNAL(a(int x)),
this, SLOT(b(int y)); // FALSCH
connect(this, SIGNAL(a(int)),
this, SLOT(b(int)); // RICHTIG
Slot-Methoden muss er stets in einem Block »private slots:«, »protected slots:« oder »public slots:« deklarieren. Beim Kompilieren erscheint keine Fehlermeldung, falls das »slots«-Keyword fehlt, da Slots für den Compiler normale gültige Methoden sind. Der umsichtige Programmierer prüft daher, ob er wirklich alle als Slot verwendeten Methoden in einem solchen Bereich deklariert hat (Regel 3):
private slots: // RICHTIG
void myCorrectSlot();
private: // FALSCH
void myWrongSlot();
Programmierer vergessen oft, Signal-Methoden den Rückgabewert »void« zuzuweisen. Geben sie stattdessen zum Beispiel »int« an, verursacht das häufig nicht einmal einen Compiler-Fehler. Der Meta Object Compiler (MOC) erzeugt für jedes definierte Signal eine Implementierung in der entsprechenden Moc-Datei, die dann allerdings nicht mehr eindeutig ist (Regel 4).
Makros für Meta-Objekte
Das »Q_OBJECT«-Makro erlaubt es Qt-Klassen, mit dem Signal-Slot-Mechanismus zu arbeiten. Es deklariert die für den Meta-Object-Mechanismus (vergleichbar mit Reflection) notwendigen Methoden, zum Beispiel »metaObject()« oder »qt_metacall(QMetaObject::Call, int, void**)«. Alternativ können Entwickler für Klassen, die keine Signale oder Slots definieren, aber trotzdem auf das »QMetaObject« zugreifen möchten, das Makro »Q_GADGET« verwenden. Es ist außerdem wichtig, das Makro »Q_OBJECT« direkt nach der Klassendefinition zu deklarieren (Regel 5):
class MyTestClass
{
Q_OBJECT
...
};
Einen leicht zu übersehenden Fehler deckt die Suchfunktion des Editors zuverlässig auf: Ein Semikolon nach dem »Q_OBJECT«-Makro verwirrt den Compiler. Die Definition des Makros verlangt, hier kein Semikolon zu setzen. Der geübte Entwickler sucht daher bei einer nichts sagenden Fehlermeldung des Compilers oder des Linkers in allen Projektdateien nach der Zeichenkette »Q_OBJECT;« und findet hier oft auch schon die Fehlerquelle (Regel 6).
class MyTestClass {
Q_OBJECT; // FALSCH
Q_OBJECT // RICHTIG
...
Wer Signale und Slots verwendet, muss die entsprechende Klasse von »QObject« oder einer seiner Ableitungen selbst ableiten. »QObject« stellt einige wichtige Methoden für diesen Mechanismus, zum Beispiel »connect()« und »metaObject()«, zur Verfügung. Achtung: Bis Qt 4 war beispielsweise »QThread« selbst nicht von »QObject« abgeleitet. Darum war es schwierig bis unmöglich, Signale und Slots in Multithreaded-Anwendungen zu verwenden (Regel 7).