Qt/QML macht C++-Klassen für QML verfügbar und warum setContextProperty nicht die beste Idee ist

 C Programming >> C-Programmierung >  >> Tags >> Qt
Qt/QML macht C++-Klassen für QML verfügbar und warum setContextProperty nicht die beste Idee ist

In diesem Artikel werde ich die verschiedenen Möglichkeiten diskutieren, eine C++-Klasse für QML verfügbar zu machen. QML ist eine Auszeichnungssprache (Teil des QT-Frameworks) wie HTML/CSS mit Inline-JavaScript, das mit dem C++-Code Ihrer (QT-)Anwendung interagieren kann. Es gibt mehrere Möglichkeiten, eine C++-Klasse für QML verfügbar zu machen, jede mit ihren eigenen Vorteilen und Macken. Dieser Leitfaden behandelt drei Integrationsmethoden, qmlRegisterSingletonType<> , rootContext->setContextProperty() und qmlRegisterType<> . Wir beenden mit einem einfachen Benchmark, der den Unterschied in den Startzeiten zwischen den ersten beiden zeigt.

Die Zusammenfassung ist das setContextProperty ist veraltet, wirkt sich auf die Leistung aus (und Sie sollten qmlRegisterSingletonType<> verwenden . In mybenchmarks der qmlRegisterSingletonType man ist schneller alssetContextProperty . Wenn Sie mehr als eine Instanz Ihrer Klasse benötigen, verwenden Sie qmlRegisterType<> und instanziieren Sie Ihre Objekte direkt in QML.qmlRegisterType ist in meinen Benchmarks auch schneller als eine Kontexteigenschaft.

Die Singleton-Methode ist meiner bescheidenen Meinung nach die beste Methode, wenn Sie eine bestimmte Instanz (wie ein Modell oder ein Ansichtsmodell) benötigen, und die registerType-Methode ist die beste Methode, wenn Sie viele Dinge in QML instanziieren müssen. Das Festlegen einer Root-Kontexteigenschaft hat mehrere Probleme, Leistung ist eines davon, ebenso wie mögliche Namenskonflikte, keine statische Analyse und es ist für jeden überall in QML verfügbar. Laut einem Qt-Fehlerbericht (QTBUG-73064) wird es in Zukunft aus QML entfernt.

Einführung

Meines Erachtens ist es besser, klare Grenzen in Ihrer Anwendung zu haben als ein verflochtenes Durcheinander, in dem alles eng mit allem verbunden ist. Bei einem Singleton oder einem Typ ist diese Trennung möglich, bei einer Stammkontexteigenschaft nicht. Für kleine Projekte ist die setContextProperty -Methode ist in Ordnung, aber die Singleton-Methode ist nicht aufwändiger, also würde ich auch in diesem Fall die Verwendung von Singletons bevorzugen.

Die Qt/QML-Dokumentation ist umfassend, aber ein Fehler, den ich finde, ist, dass das Framework keine (empfohlene) Art hat, Dinge zu tun. Sie können alle Methodenparameter und möglichen Optionen finden, aber wenn Sie wissen wollen, wie man die Farbe des Textes auf einem Button{} ändert , viel Glück beim Suchen auf StackOverflow. Gleiches gilt für die Integration von C++ mit QML. Die Qt-Dokumentation bietet einen Überblick über verschiedene Integrationsmethoden, sagt Ihnen aber nicht, welche die beste ist. Es sagt Ihnen nur, was möglich ist, und überlässt es Ihnen, zu entscheiden. Es gibt Flussdiagramme, die Ihnen helfen, welche Methode Sie verwenden sollen, aber fast alle Anleitungen und Beispiele online verwenden einfach rootContext->setContextProperty() . Sogar mein eigener Artikel über Signale und Slots nutzt das, wegen der Einfachheit für kleine Projekte.

QML sollte keine Kenntnis der Domäne haben, es ist nur eine UI-Markupsprache, daher sollte die eigentliche Arbeit oder Logik auf der C++-Seite erfolgen, nicht über QML/JavaScript. Die Verwendung von JavaScript wird sehr schnell chaotisch und kann nicht über Unit-Tests getestet werden, daher ist die Verwendung für mich ein großes Nein, nein. Genau wie bei WPF undXAML Auf Microsoft-Seite sollte Ihre Benutzeroberfläche nur wenige Bindungen an viewModel haben und keinen eigenen Code oder eigene Logik. Ich habe ganze Zustandsmaschinen und komplexe JavaScript-Methoden in QML gesehen, die so komplex waren, dass ich immer noch Alpträume davon habe. Alle diese Funktionen könnten einfach in C++ ausgeführt werden, wo sie mit Komponententests getestet werden könnten. Ich wette mit dir, dass sie auch schneller wären.

Der Grund für das Schreiben dieses Artikels ist, dass ich mich mit den verschiedenen Optionen zur C++-Integration in QML beschäftigt habe. Bei der Arbeit haben wir kürzlich aus Leistungsgründen einen ganzen Haufen QML-Code umgestaltet, wobei das Weglassen einer globalen Kontexteigenschaft immens geholfen hat. Ich habe auch einen Großteil unseres Codes und unserer Assets benannt und bin auf mehr als ein Problem mit fehlender oder falscher Qt-Dokumentation gestoßen. Unser Code wird als statische Anwendung und als staticlib kompiliert im Fall von Bibliotheken, einschließlich aller Assets in einem qrc Datei. Diese statischen Kompilierungs- und Dateisystempfade, die fast mit meinem qmldir übereinstimmten Namen (Nichtübereinstimmung von Großbuchstaben) in Kombination mit falscher Dokumentation bereiteten viele Kopfschmerzen, aber am Ende habe ich alles wieder in Ordnung gebracht, was eine merkliche Erhöhung der Antwortzeiten für den Benutzer zeigte.

Den Beispiel-Quellcode für dieses Projekt finden Sie hier auf meinem Github.

QML-Beispiel für Ampeln

Ich habe ein einfaches QML-Beispiel mit einer Ampel und einigen Schaltflächen zur Steuerung dieser Ampel erstellt. Die TrafficLightQml Objekt ist ein Rechteck mit 3 Kreisen darin, jeder in einer anderen Farbe. Drei Eigenschaften werden offengelegt, um die verschiedenen Lampen ein- oder auszuschalten. Dies ist ein opacity gesteuert durch abool , um die Dinge einfach zu halten. Nicht das beste Beispiel, eine Zustandsmaschine würde beides tun, aber um es für diesen Artikel einfach zu halten, habe ich entschieden, dass dies in Ordnung ist.

Die TrafficLightQmlControlButtons enthält zwei Tasten und stellt eine Eigenschaft und ein Signal zur Verfügung. Eigentlich zwei Signale, da Eigenschaften einen implizit generierten onXXXChanged haben Signal. Eine Taste schaltet das Licht ein oder aus und eine Taste schaltet durch die verschiedenen Lampen im Muster der niederländischen Ampeln:

Red (stop) -> Green (go) -> Orange (caution, almost Red)

Warum Eigenschaften und Signale offenlegen, anstatt die relevanten Funktionen innerhalb der TrafficLight-QML selbst aufzurufen? Das würde QMLcontrol eng an das C++-Gegenstück und die Belichtungsmethode koppeln. Indem ich das QML-Steuerelement generisch genug mache, kann ich die Implementierung jederzeit austauschen. Die Benutzeroberfläche muss nur wissen, wie sie aussieht und was zu tun ist, nicht wie oder wann es zu tun ist. Dadurch wird das Unit-Testen des Verhaltens viel einfacher, da es keine Intelligenz im QML-Steuerelement gibt, Sie müssen das nicht testen. Wir sollten darauf vertrauen können, dass das Framework bei der Weitergabe von Signalen und Methoden funktioniert. Die Kernlogik, z. B. welches Lampenmuster oder wann ein- oder ausgeschaltet werden soll, sollte einem Unit-Test unterzogen werden, was beispielsweise mit Qt Test oder GoogleTest einfach zu bewerkstelligen ist. Das Testen einer QML-Steuerung/Javascript-Funktion ist viel schwieriger.

Der main.qml Die Datei hat 4 Instanzen dieser beiden Steuerelemente, aber bei jedem sind die Eigenschaften und Signale an unterschiedliche C++-Objekte gebunden. Auf diese Weise können Sie deutlich sehen, wie Sie sie verwenden, einschließlich ihrer Erstellung und Weitergabe in main.cpp .

Die Datei- und Klassennamen sind sehr ausführlich, um Ihnen zu zeigen, was wann und wo verwendet wird. Wenn alles (qml, c++, ids) trafficlight genannt wurde , dass Sichtbarkeit und Einsicht verloren gehen. Jetzt ist sehr klar, welche Zeile sich auf welche Komponente bezieht, sowohl in QML als auch in C++.

setContextProperty

Beginnen wir mit dem beliebtesten Beispiel, fast jedes Tutorial, das Sie finden, verwendet es. Sogar in der offiziellen Qt-Dokumentation zu Best Practices, Abschnitt Pushing References to QML verwenden sie einen setContextProperty .

Bei Verwendung von setContextProperty , ist die Eigenschaft für jede von der QML-Engine geladene Komponente verfügbar. Kontexteigenschaften sind nützlich für Objekte, die verfügbar sein müssen, sobald die QML geladen ist, und nicht in QML instanziiert werden können.

In meinem Ampelbeispiel sieht das bei main.cpp so aus

TrafficLightClass trafficLightContext;
qmlRegisterUncreatableType<TrafficLightClass>("org.raymii.RoadObjectUncreatableType", 1, 0, "TrafficLightUncreatableType", "Only for enum access");
engine.rootContext()->setContextProperty("trafficLightContextProperty", &trafficLightContext);

In (jedem) QML kann ich es so verwenden:

Component.onCompleted: { trafficLightContextProperty.nextLamp(); // call a method } 
redActive: trafficLightContextProperty.lamp === TrafficLightUncreatableType.Red // use a property

Keine Einfuhrerklärung erforderlich. Weiter hinten im Artikel gibt es einen Abschnitt über Enums, der den UncreatebleType erklärt siehst du oben. Sie können diesen Teil überspringen, wenn Sie nicht vorhaben, Aufzählungen aus Ihrer Klasse auf der QML-Seite zu verwenden.

Es ist im Moment nichts grundsätzlich falsch daran, diesen Ansatz zu verwenden, um eine C++-Klasse in QML zu erhalten. Für kleine Projekte oder Projekte, bei denen die Leistung keine Rolle spielt, ist die Kontexteigenschaft in Ordnung. Im Großen und Ganzen sprechen wir über die -ilitäten, wie Wartbarkeit, aber für ein kleines Projekt ist das wahrscheinlich nicht so wichtig wie in einem Projekt mit einer größeren Codebasis oder mehreren Teams, die daran arbeiten.

Warum ist eine Kontexteigenschaft dann schlecht?

Es gibt ein paar Nachteile im Vergleich zum Singleton- oder RegisterType-Ansatz. Es gibt einen Qt-Bug, der die zukünftige Entfernung von Kontexteigenschaften verfolgt, ein StackOverflow-Beitrag und ein QML-Codierungsleitfaden geben eine großartige Zusammenfassung. Die QML-Dokumentation erwähnt diese Punkte auch, aber in a weniger offensichtlich, daher ist die Zusammenfassung schön.

Zitieren des Qt-Fehlers (QTBUG-73064):

Das Problem mit Kontexteigenschaften besteht darin, dass sie den Zustand „magisch“ in Ihr QML-Programm einfügen. Ihre QML-Dokumente erklären nicht, dass sie diesen Zustand benötigen, aber ohne funktionieren sie normalerweise nicht. Sobald die Kontexteigenschaften vorhanden sind, können Sie sie verwenden, aber kein Werkzeug kann richtig nachverfolgen, wo sie hinzugefügt wurden und wo sie entfernt wurden (oder entfernt werden sollten). Kontexteigenschaften sind für QML-Werkzeuge unsichtbar und die Dokumente, die sie verwenden, können nicht statisch validiert werden.

Zitieren des QML-Codierungsleitfadens:

Kontexteigenschaften nehmen immer einen QVariant auf oder QObject , was bedeutet, dass jedes Mal, wenn Sie auf die Eigenschaft zugreifen, sie neu ausgewertet wird, da die Eigenschaft zwischen jedem Zugriff als setContextProperty() geändert werden kann kann jederzeit verwendet werden.

Der Zugriff auf Kontexteigenschaften ist kostspielig und es ist schwierig, mit ihnen zu argumentieren. Wenn Sie QML-Code schreiben, sollten Sie danach streben, die Verwendung von kontextabhängigen Variablen (eine Variable, die nicht im unmittelbaren Gültigkeitsbereich existiert, sondern von der darüber liegenden.) und von globalen Zuständen zu reduzieren. Jedes QML-Dokument sollte in der Lage sein, mit QMLscene zu laufen, vorausgesetzt, dass die erforderlichen Eigenschaften gesetzt sind.

Zitieren dieser Antwort von StackOverflow bezüglich Problemen mit setContextProperty :

setContextProperty setzt das Objekt als Wert einer Eigenschaft direkt im Wurzelknoten Ihres QML-Baums, also sieht es im Grunde so aus:

property var myContextProperty: MySetContextObject {}
ApplicationWindow { ... }

Dies hat verschiedene Auswirkungen:

  • Sie müssen dateiübergreifende Verweise auf Dateien ermöglichen, die nicht "lokal" zueinander sind (main.cpp und wo auch immer Sie versuchen, es zu verwenden)
  • Namen werden leicht überschattet. Wenn der Name der Kontexteigenschaft an anderer Stelle verwendet wird, können Sie ihn nicht auflösen.
  • Für die Namensauflösung kriechen Sie durch einen möglichen tiefen Objektbaum, immer auf der Suche nach der Eigenschaft mit Ihrem Namen, bis er schließlich die Kontexteigenschaft in der Wurzel findet. Das ist vielleicht etwas ineffizient - aber wahrscheinlich kein großer Unterschied.

qmlRegisterSingletonType andererseits können Sie die Daten dort importieren, wo Sie sie benötigen. Sie könnten also von einer schnelleren Namensauflösung profitieren, ein Shadowing der Namen ist praktisch unmöglich und Sie haben keine intransparenten Querverweise zwischen Dateien.

Nachdem Sie nun eine Reihe von Gründen gesehen haben, warum Sie eine Kontexteigenschaft so gut wie nie verwenden sollten, wollen wir damit fortfahren, wie Sie eine einzelne Instanz einer Klasse QML aussetzen sollten.

qmlRegisterSingletonType<>

Ein Singleton-Typ ermöglicht das Offenlegen von Eigenschaften, Signalen und Methoden in einem Namensraum, ohne dass der Client eine Objektinstanz manuell instanziieren muss. QObject Singleton-Typen sind eine effiziente und bequeme Möglichkeit, Funktionalität oder globale Eigenschaftswerte bereitzustellen. Einmal registriert, ein QObject Singleton-Typ sollte importiert und wie jeder andere QObject verwendet werden Instanz QML ausgesetzt.

Also im Grunde dasselbe wie die Kontexteigenschaft, außer dass Sie sie importieren müssen in QML. Das ist für mich der wichtigste Grund, Singletonsover-Kontexteigenschaften zu verwenden. In den vorangegangenen Abschnitten habe ich bereits Unterschiede und Nachteile von Kontexteigenschaften genannt, daher werde ich mich hier nicht wiederholen.

Im Beispiel-Ampelcode ist dies der relevante Code in main.cpp :

TrafficLightClass trafficLightSingleton;
qmlRegisterSingletonType<TrafficLightClass>("org.raymii.RoadObjects", 1, 0, "TrafficLightSingleton",
                                     [&](QQmlEngine *, QJSEngine *) -> QObject * {
    return &trafficLightSingleton;
    // the QML engine takes ownership of the singleton so you can also do:
    // return new trafficLightClass;
});

Auf der QML-Seite müssen Sie das Modul importieren, bevor Sie es verwenden können:

import org.raymii.RoadObjects 1.0

Anwendungsbeispiel:

Component.onCompleted: { TrafficLightSingleton.nextLamp() // call a method }
redActive: TrafficLightSingleton.lamp === TrafficLightSingleton.Red; // use a property

Keine Enum-Verrücktheit mit UncreatableTypes in diesem Fall.

qmlRegisterType

Alle vorherigen Absätze haben ein einzelnes vorhandenes C++-Objekt für QML verfügbar gemacht. Das ist meistens in Ordnung, wir bei der Arbeit legen unseren models offen und viewmodels auf diesem Weg zu QML. Was aber, wenn Sie mehr als eine Instanz eines C++-Objekts in QML erstellen und verwenden müssen? In diesem Fall können Sie die gesamte Klasse über qmlRegisterType<> für QML verfügbar machen , in unserem Beispiel in main.cpp :

qmlRegisterType<TrafficLight>("org.raymii.RoadObjectType", 1, 0, "TrafficLightType");

Auf der QML-Seite müssen Sie es erneut importieren:

import org.raymii.RoadObjectType 1.0

Die Verwendung ist wie in den anderen Beispielen, mit dem Zusatz, eine Instanz Ihres Objekts zu erstellen:

TrafficLightType {
    id: trafficLightTypeInstance1
}

TrafficLightType {
    id: trafficLightTypeInstance2
}

Im obigen Beispiel habe ich zwei Instanzen dieses C++-Typs in QML erstellt, ohne manuell eine zu erstellen und diese Instanz in main.cpp verfügbar zu machen . Die Verwendung ist fast die gleiche wie im Singleton:

redActive: trafficLightTypeInstance1.lamp === TrafficLightType.Red; // use a property
Component.onCompleted: { trafficLightTypeInstance1.nextLamp() // call a method }

Und für unsere zweite Instanz:

redActive: trafficLightTypeInstance2.lamp === TrafficLightType.Red; // use a property
Component.onCompleted: { trafficLightTypeInstance2.nextLamp() // call a method }

Der einzige Unterschied ist die ID, trafficLightTypeInstance1 gegenüber trafficLightTypeInstance2 .

Wenn Sie viele Dinge haben möchten, stellen Sie die gesamte Klasse über qmlRegisterType bereit ist viel bequemer, als all diese Dinge manuell in C++ zu erstellen, sie dann als Singletons verfügbar zu machen und sie schließlich in QML zu importieren.

Merkwürdigkeiten bei setContextProperty und Aufzählungen

In der Beispiel-Ampelklasse haben wir einen enum class für LampState . Die Lampe kann Off sein oder eine der drei Farben. Bei der Registrierung des Typs als Singleton funktioniert die folgende QML-Eigenschaftszuweisung über eine boolesche Auswertung:

redActive: TrafficLightSingleton.lamp === TrafficLightSingleton.Red

lamp ist ein exponierter Q_PROPERTY mit einem angehängten Signal bei Änderung. Red ist Teil des enum class .

Allerdings, wenn dieselbe Property-Anweisung mit der über setContextProperty registrierten Instanz verwendet wird , Folgendes funktioniert nicht:

redActive: trafficLightContextProperty.lamp === trafficLightContextProperty.Red

Führt zu einem vagen Fehler wie qrc:/main.qml:92: TypeError: Cannot read property 'lamp' of null und die Eigenschaft wird nie auf true gesetzt. Ich habe viele verschiedene Lösungen ausprobiert, wie das Aufrufen der Getter-Funktion des verwendeten QML-Signals (.getLamp() ) und Debugging in Component.onCompleted() . AQ_INVOKABLE debug-Methode für die Klasse funktioniert gut, aber der Enum-Wert gibt undefined zurück . Andere Aufrufe von Slots, wie .nextLamp() funktioniert einwandfrei, nur die Enum-Werte sind nicht zugänglich.

Dies ist im Flussdiagramm und in der Dokumentation aufgeführt, aber ich wette, Sie sind frustriert, bevor Sie das herausgefunden haben.

Qt Creator kennt die Werte, es versucht sogar, sie automatisch auszufüllen, und die Fehlermeldungen sind überhaupt nicht hilfreich. Versuchen Sie nicht, sie automatisch auszufüllen, wenn ich sie verwenden kann, oder geben Sie eine hilfreiche Fehlermeldung aus, wäre mein Vorschlag für jeden, der Qt Creator entwickelt.

Die Lösung dafür ist, wie in der Dokumentation aufgeführt, die gesamte Klasse als UncreatableType zu registrieren :

Sometimes a QObject-derived class may need to be registered with the QML
type system but not as an instantiable type. For example, this is the
case if a C++ class:

    is an interface type that should not be instantiable
    is a base class type that does not need to be exposed to QML
    **declares some enum that should be accessible from QML, but otherwise should not be instantiable**
    is a type that should be provided to QML through a singleton instance, and should not be instantiable from QML

Das Registrieren eines nicht erzeugbaren Typs erlaubt Ihnen, die Enum-Werte zu verwenden, aber Sie können keinen TrafficLightType {} instanziieren QML-Objekt. Das erlaubt Ihnen auch, einen Grund anzugeben, warum die Klasse nicht erstellbar ist, sehr praktisch für zukünftige Referenzen:

qmlRegisterUncreatableType<TrafficLight("org.raymii.RoadObjectType", 1, 0, "TrafficLightType", "Only for enum access");

In Ihre QML-Datei müssen Sie nun den Typ:

importieren
import org.raymii.RoadObjectType 1.0

Danach können Sie die Enum-Werte in einem Vergleich verwenden:

redActive: trafficLightContextProperty.lamp === TrafficLightType.Red

Wenn Sie all diese zusätzliche Arbeit investieren, um den Typ zu registrieren, warum verwenden Sie nicht einfach die Singleton-Implementierung. Wenn Sie enums nicht verwenden Sie können mit setContextProperty() davonkommen , aber dennoch. Etwas nur dann zu importieren, wenn man es braucht, anstatt es überall und jederzeit verfügbar zu haben, fühlt sich für mich viel besser an.

Warum nicht QML_ELEMENT / QML_UNCREATABLE / QML_INTERFACE / QML_SINGLETON ?

In Qt 5.15 wurden einige neue Methoden verfügbar gemacht, um C++ mit QML zu integrieren. Diese arbeiten mit einem Makro in Ihrer Header-Datei und einer zusätzlichen Definition in Ihrem .pro Datei.

QML_ELEMENT / QML_UNCREATABLE / QML_INTERFACE / QML_SINGLETON / QML_ANONYMOUS

Im neuesten 5.15-Doc-Snapshot und im Blogpost werden diese Methoden erklärt, sie sollten ein Problem lösen, das auftreten könnte, nämlich dass Sie Ihren C++-Code mit Ihren QML-Registrierungen synchron halten müssen. Zitieren des Blogposts:

Dann gehen sie auf einige weitere (gültige) technische Details ein.

Der Grund, warum ich diese nicht in diesen Vergleich einbeziehe, ist, dass sie neu sind, nur in Qt 5.15 und höher verfügbar sind und von .pro abhängen Dateien und damit auf qmake . cmake-Unterstützung ist nicht verfügbar, nicht einmal in Qt 6.0.

Wenn Ihre Codebasis neu genug ist, um auf dieser neuesten Qt 5.15-Version ausgeführt zu werden, oder wenn Sie 6+ ausführen, sind diese neuen Methoden besser als die oben aufgeführten. Bitte lesen Sie den technischen Teil des Blogposts, warum. Wenn Sie können, also wenn Ihre Qt-Version und Ihr Build-System (qmake ) erlaubt, verwenden Sie am besten QML_SINGLETON und Freunde.

Ich habe ein kleines Beispiel geschrieben, um dasselbe wie qmlRegisterType<> zu erreichen unten als Referenz. In Ihrem .pro Datei fügen Sie ein zusätzliches CONFIG+= hinzu parameter(qmptypes ) und zwei weitere neue Parameter:

CONFIG += qmltypes
QML_IMPORT_NAME = org.raymii.RoadObjects
QML_IMPORT_MAJOR_VERSION = 1    

In Ihrem .cpp Klasse, in unserem Fall TrafficLightClass.h , fügen Sie Folgendes hinzu:

#include <QtQml>
[...]
// below Q_OBJECT
QML_ELEMENT

Wenn Sie denselben Effekt wie bei qmlRegisterSingleton wünschen , fügen Sie QML_SINGLETON hinzu unter dem QML_ELEMENT Linie. Es erstellt ein standardmäßig konstruiertes Singleton.

Importieren Sie in Ihrer QML-Datei den registrierten Typ:

import org.raymii.RoadObjects 1.0

Sie können sie dann in QML anhand ihres Klassennamens verwenden (kein separater Name wie oben):

TrafficLightClass {
    [...]
}

Bechmarking-Startzeit

Um sicherzugehen, ob das, was wir tun, tatsächlich einen Unterschied macht, habe ich einen einfachen Benchmark erstellt. Die einzige Möglichkeit sicherzustellen, dass etwas schneller ist, besteht darin, es zu profilieren. Der Qt-Profiler spielt in einer eigenen Liga, daher werde ich einen einfacheren Test verwenden.

Selbst wenn sich herausstellen sollte, dass die Singleton-Variante langsamer ist, würde ich sie aus den gleichen Gründen wie zuvor der globalen Eigenschaft vorziehen. (Falls Sie sich fragen, ich habe diesen Abschnitt geschrieben, bevor ich die Benchmarks gemacht habe.)

Die erste Zeile in main.cpp gibt die aktuelle Epoche in Millisekunden aus und auf der QML-Seite im Root-Fenster habe ich einen Component.onCompleted hinzugefügt Handler, der auch die aktuelle Epoche in Millisekunden ausgibt und dann Qt.Quit aufruft um die Anwendung zu beenden. Das Subtrahieren dieser beiden Epochen-Zeitstempel gibt mir Startlaufzeit, mache das ein paar Mal und nimm den Durchschnitt, für die Version mit nur einem qmlRegisterSingleton und die Version mit nur einem rootContext->setProperty() .

Der Build hat den Qt Quick-Compiler aktiviert und ist ein Release-Build. Es wurden keine anderen QML-Komponenten geladen, kein Exit-Button, kein Hilfetext, nur ein Fenster mit einem TrafficLightQML und die Knöpfe. Die Ampel-QML hat ein onCompleted, das die C++-Ampel einschaltet.

Beachten Sie, dass dieser Benchmark nur ein Anhaltspunkt ist. Wenn Sie Probleme mit der Anwendungsleistung haben, empfehle ich Ihnen, den Qt Profiler zu verwenden, um herauszufinden, was los ist. Qt hat einen Artikel zur Leistung, der Ihnen ebenfalls helfen kann.

Drucken des Zeitstempels der Epoche in main.cpp :

#include <iostream>
#include <QDateTime>
[...]
std::cout << QDateTime::currentMSecsSinceEpoch() << std::endl;

Drucken Sie es in main.qml :

Window {
    [...]
    Component.onCompleted: {
        console.log(Date.now())
    }
}

Mit grep und eine Regex, um nur den Zeitstempel zu erhalten und ihn dann mit tac umzukehren (umgekehrt cat ), dann mit awk um die beiden Zahlen zu subtrahieren. Wiederholen Sie das fünfmal und verwenden Sie awk erneut, um die durchschnittliche Zeit in Millisekunden zu erhalten:

for i in $(seq 1 5); do 
    /home/remy/tmp/build-exposeExample-Desktop-Release/exposeExample 2>&1 | \
    grep -oE "[0-9]{13}" | \
    tac | \
    awk 'NR==1 { s = $1; next } { s -= $1 } END { print s }'; 
done | \
awk '{ total += $1; count++ } END { print total/count }'
  • Der Durchschnitt für qmlRegisterSingleton<> Beispiel:420 ms

  • Der Durchschnitt für qmlRegisterType<> Beispiel:492,6 ms

  • Der Durchschnitt für rootContext->setContextProperty Beispiel:582,8 ms

Wenn Sie den obigen Benchmark fünfmal durchlaufen und diese Durchschnittswerte mitteln, ergeben sich 439,88 ms für den Singleton, 471,68 ms für den registerType und 572,28 ms für die rootContext-Eigenschaft.

Dieses einfache Beispiel zeigt bereits einen Unterschied von 130 bis 160 ms für eine Singleton-Variable. Sogar das Registrieren und Instanziieren eines Typs in QML ist schneller als eine Kontexteigenschaft. (Habe eigentlich keinen solchen Unterschied erwartet)

Dieser Benchmark wurde auf einem Raspberry Pi 4, Qt 5.15 durchgeführt, und während dieser lief, liefen keine anderen Anwendungen außer IceWM (Fenstermanager) und xterm (Terminal-Emulator).

Ich habe diesen Vorgang mit unserer Arbeitsanwendung wiederholt, die ein ziemlich großes und komplexes Objekt mit etwa einer Megazillion Eigenschaftsbindungen hat (tatsächliche Anzahl, habe sie beim Refactoring selbst gezählt), und dort betrug der Unterschied mehr als 2 Sekunden.

Bitte führen Sie jedoch selbst ein paar Benchmarks auf Ihrem eigenen Computer mit Ihrem eigenen Code durch, bevor Sie die obigen Messungen als absolute Quelle der Wahrheit betrachten.

Und wenn Sie einen einfachen Weg kennen, die Startzeit mit dem Qt-Profiler ein paar Mal zu messen und den Durchschnitt zu ermitteln, der einfacher ist, als die gesamte Liste manuell zu durchsuchen, senden Sie mir eine E-Mail.