Auf dieser Seite

Warum verwendet Qt Moc für Signale und Slots?

Templates sind ein in C++ integrierter Mechanismus, der es dem Compiler ermöglicht, Code je nach Typ der übergebenen Argumente dynamisch zu generieren. Daher sind Templates für Framework-Entwickler äußerst interessant, und wir setzen an vielen Stellen in Qt fortgeschrittene Templates ein. Es gibt jedoch Einschränkungen: Es gibt Dinge, die sich mit Templates leicht ausdrücken lassen, und es gibt Dinge, die sich mit Templates unmöglich ausdrücken lassen. Eine generische Vektor-Containerklasse lässt sich leicht ausdrücken, sogar mit partieller Spezialisierung für Zeigertypen, während eine Funktion, die eine grafische Benutzeroberfläche auf der Grundlage einer als String angegebenen XML-Beschreibung einrichtet, nicht als Template ausdrückbar ist. Und dann gibt es noch eine Grauzone dazwischen. Dinge, die man mit Vorlagen „hacken“ kann – auf Kosten von Codegröße, Lesbarkeit, Portabilität, Benutzerfreundlichkeit, Erweiterbarkeit, Robustheit und letztlich der Schönheit des Designs. Sowohl Vorlagen als auch der C-Präprozessor lassen sich so weit ausreizen, dass sie unglaublich clevere und verblüffende Dinge leisten können. Aber nur weil diese Dinge möglich sind, bedeutet das nicht zwangsläufig, dass ihre Umsetzung die richtige Designentscheidung ist. Code ist leider nicht dazu gedacht, in Büchern veröffentlicht zu werden, sondern mit realen Compilern auf realen Betriebssystemen kompiliert zu werden.

Hier sind einige Gründe, warum Qt den moc verwendet:

Syntax ist wichtig

Syntax ist nicht nur „Zucker“: Die Syntax, mit der wir unsere Algorithmen ausdrücken, kann die Lesbarkeit und Wartbarkeit unseres Codes erheblich beeinflussen. Die für die Signale und Slots von Qt verwendete Syntax hat sich in der Praxis als sehr erfolgreich erwiesen. Die Syntax ist intuitiv, einfach zu verwenden und leicht zu lesen. Qt-Einsteiger finden, dass die Syntax ihnen hilft, das Konzept der Signale und Slots zu verstehen und anzuwenden – trotz dessen hochgradig abstrakter und generischer Natur. Dies hilft Programmierern, ihr Design von Anfang an richtig zu gestalten, ohne überhaupt über Entwurfsmuster nachdenken zu müssen.

Code-Generatoren sind gut

Der „ moc “ (Meta-Object Compiler) von Qt bietet eine elegante Möglichkeit, über die Möglichkeiten der kompilierten Sprache hinauszugehen. Dies geschieht durch die Generierung von zusätzlichem C++-Code, der von jedem Standard-C++-Compiler kompiliert werden kann. Der „ moc “ liest C++-Quelldateien ein. Findet er eine oder mehrere Klassendeklarationen, die das Makro „ Q_OBJECT “ enthalten, erzeugt er eine weitere C++-Quelldatei, die den Metaobjektcode für diese Klassen enthält. Die vom „ moc “ generierte C++-Quelldatei muss kompiliert und mit der Implementierung der Klasse verknüpft werden (oder sie kann per „ #included “ in die Quelldatei der Klasse eingebunden werden). In der Regel wird „ moc “ nicht manuell, sondern automatisch vom Build-System aufgerufen, sodass für den Programmierer kein zusätzlicher Aufwand entsteht.

Der „ moc “ ist nicht der einzige Codegenerator, den Qt verwendet. Ein weiteres prominentes Beispiel ist der „ uic “ (User Interface Compiler). Er nimmt eine Beschreibung der Benutzeroberfläche im XML-Format entgegen und erstellt C++-Code, der das Formular einrichtet. Auch außerhalb von Qt sind Codegeneratoren weit verbreitet. Nehmen wir zum Beispiel rpc und idl, die es Programmen oder Objekten ermöglichen, über Prozess- oder Maschinengrenzen hinweg zu kommunizieren. Oder die große Vielfalt an Scanner- und Parser-Generatoren, wobei lex und yacc die bekanntesten sind. Sie nehmen eine Grammatik-Spezifikation als Eingabe entgegen und generieren Code, der eine Zustandsmaschine implementiert. Die Alternativen zu Codegeneratoren sind selbst programmierte Compiler, proprietäre Sprachen oder grafische Programmierwerkzeuge mit Einweg-Dialogen oder Assistenten, die undurchsichtigen Code bereits während der Entwurfsphase statt erst zur Kompilierungszeit generieren. Anstatt unsere Kunden an einen proprietären C++-Compiler oder eine bestimmte integrierte Entwicklungsumgebung zu binden, ermöglichen wir ihnen, die Werkzeuge ihrer Wahl zu nutzen. Anstatt Programmierer zu zwingen, generierten Code in Quellcode-Repositorys einzufügen, ermutigen wir sie, unsere Werkzeuge in ihr Build-System zu integrieren: sauberer, sicherer und ganz im Sinne von UNIX.

GUIs sind dynamisch

C++ ist eine standardisierte, leistungsstarke und ausgefeilte Allzwecksprache. Es ist die einzige Sprache, die in einem so breiten Spektrum von Softwareprojekten zum Einsatz kommt und jede Art von Anwendung abdeckt – von kompletten Betriebssystemen über Datenbankserver und High-End-Grafikanwendungen bis hin zu gängigen Desktop-Anwendungen. Einer der Schlüssel zum Erfolg von C++ ist sein skalierbares Sprachdesign, das auf maximale Leistung und minimalen Speicherverbrauch ausgerichtet ist und gleichzeitig die ANSI-C-Kompatibilität gewährleistet.

All diesen Vorteilen stehen jedoch auch einige Nachteile gegenüber. Für C++ ist das statische Objektmodell ein klarer Nachteil gegenüber dem dynamischen Messaging-Ansatz von Objective C, wenn es um die komponentenbasierte Programmierung grafischer Benutzeroberflächen geht. Was für einen High-End-Datenbankserver oder ein Betriebssystem gut ist, ist nicht unbedingt die richtige Designentscheidung für ein GUI-Frontend. Mit „ moc “ haben wir diesen Nachteil in einen Vorteil verwandelt und die Flexibilität hinzugefügt, die erforderlich ist, um die Herausforderung einer sicheren und effizienten Programmierung grafischer Benutzeroberflächen zu meistern.

Unser Ansatz geht weit über das hinaus, was man mit Vorlagen erreichen kann. So können wir beispielsweise Objekteigenschaften definieren. Und wir können Signale und Slots überladen, was sich bei der Programmierung in einer Sprache, in der Überladung ein Schlüsselkonzept ist, ganz natürlich anfühlt. Unsere Signale erhöhen die Größe einer Klasseninstanz um null Bytes, was bedeutet, dass wir neue Signale hinzufügen können, ohne die Binärkompatibilität zu beeinträchtigen.

Ein weiterer Vorteil ist, dass wir die Signale und Slots eines Objekts zur Laufzeit untersuchen können. Wir können Verbindungen mithilfe von typsicherem „Call-by-Name“ herstellen, ohne die genauen Typen der Objekte kennen zu müssen, die wir verbinden. Dies ist mit einer templatbasierten Lösung unmöglich. Diese Art der Laufzeitintrospektion eröffnet neue Möglichkeiten, zum Beispiel GUIs, die aus den XML-UI-Dateien v Qt Widgets -Designer generiert und verbunden werden.

Aufrufleistung ist nicht alles

Die Implementierung von Signalen und Slots in Qt ist nicht so schnell wie eine templatebasierte Lösung. Während das Auslösen eines Signals bei gängigen Template-Implementierungen etwa den Aufwand von vier gewöhnlichen Funktionsaufrufen erfordert, ist bei Qt ein Aufwand erforderlich, der etwa zehn Funktionsaufrufen entspricht. Dies ist nicht überraschend, da der Qt-Mechanismus einen generischen Marshaller, Introspektion, in Warteschlangen gestellte Aufrufe zwischen verschiedenen Threads und letztlich Skriptfähigkeit umfasst. Er stützt sich nicht auf übermäßiges Inlining und Code-Expansion und bietet unübertroffene Laufzeitsicherheit. Die Iteratoren von Qt sind sicher, während dies bei schnelleren, auf Templates basierenden Systemen nicht der Fall ist. Selbst während des Aussendens eines Signals an mehrere Empfänger können diese Empfänger sicher gelöscht werden, ohne dass Ihr Programm abstürzt. Ohne diese Sicherheit würde Ihre Anwendung letztendlich mit einem schwer zu debuggenden Lese- oder Schreibfehler im freigegebenen Speicher abstürzen.

Könnte eine template-basierte Lösung dennoch die Leistung einer Anwendung verbessern, die Signale und Slots nutzt? Es stimmt zwar, dass Qt den Aufwand für den Aufruf eines Slots über ein Signal geringfügig erhöht, doch macht der Aufwand für den Aufruf nur einen kleinen Teil der Gesamtkosten eines Slots aus. Benchmarks im Vergleich zum Signal- und Slot-System von Qt werden in der Regel mit leeren Slots durchgeführt. Sobald Sie in Ihren Slots etwas Sinnvolles tun, zum Beispiel ein paar einfache Zeichenfolgenoperationen, wird der Aufruf-Overhead vernachlässigbar. Das Qt-System ist so optimiert, dass alles, was den Operator „new“ oder „delete“ erfordert (zum Beispiel Zeichenfolgenoperationen oder das Einfügen bzw. Entfernen von Elementen aus einem Template-Container), deutlich aufwendiger ist als das Auslösen eines Signals.

Nebenbei bemerkt: Wenn Sie eine Signal-und-Slots-Verbindung in einer engen inneren Schleife einer leistungskritischen Aufgabe haben und diese Verbindung als Engpass identifizieren, sollten Sie darüber nachdenken, statt Signalen und Slots das Standardmuster der Listener-Schnittstelle zu verwenden. In solchen Fällen benötigen Sie wahrscheinlich ohnehin nur eine 1:1-Verbindung. Wenn du beispielsweise ein Objekt hast, das Daten aus dem Netzwerk herunterlädt, ist es durchaus sinnvoll, ein Signal zu verwenden, um anzuzeigen, dass die angeforderten Daten eingetroffen sind. Wenn du jedoch jedes einzelne Byte nacheinander an einen Verbraucher senden musst, verwende lieber eine Listener-Schnittstelle statt „Signals and Slots“.

Keine Einschränkungen

Da wir über die „ moc “ für Signale und Slots verfügten, konnten wir weitere nützliche Funktionen hinzufügen, die mit Vorlagen nicht realisierbar gewesen wären. Dazu gehören bereichsbezogene Übersetzungen über eine generierte „ tr() “-Funktion sowie ein erweitertes Eigenschaftssystem mit Introspektion und erweiterten Laufzeit-Typinformationen. Schon das Eigenschaftssystem allein ist ein großer Vorteil: Ein leistungsstarkes und generisches Werkzeug zur Gestaltung von Benutzeroberflächen wie der „ Qt Widgets Designer“ wäre ohne ein leistungsstarkes und introspektives Eigenschaftssystem viel schwieriger – wenn nicht gar unmöglich – zu schreiben. Doch damit ist es noch nicht getan. Wir stellen außerdem einen dynamischen Mechanismus „ qobject_cast<T>()“ bereit, der nicht auf die RTTI des Systems angewiesen ist und somit dessen Einschränkungen nicht teilt. Wir nutzen ihn, um Schnittstellen von dynamisch geladenen Komponenten sicher abzufragen. Ein weiterer Anwendungsbereich sind dynamische Metaobjekte. Wir können beispielsweise ActiveX-Komponenten nehmen und zur Laufzeit ein Metaobjekt um sie herum erstellen. Oder wir können Qt-Komponenten als ActiveX-Komponenten exportieren, indem wir deren Metaobjekt exportieren. Mit Vorlagen ist keines dieser beiden Verfahren möglich.

C++ in Verbindung mit dem „ moc “ bietet uns im Wesentlichen die Flexibilität von Objective-C oder einer Java-Laufzeitumgebung, während die einzigartigen Leistungs- und Skalierbarkeitsvorteile von C++ erhalten bleiben. Das macht Qt zu dem flexiblen und komfortablen Werkzeug, das wir heute kennen.

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.