Thread-Safe Global Singletons with Q_GLOBAL_STATIC
Q_GLOBAL_STATIC provides a thread-safe and lazily initialized global singleton managed by Qt:
Q_GLOBAL_STATIC(Type, instanceName);
Q_GLOBAL_STATIC_WITH_ARGS(Type, instanceName, Args...);
Type: The class type of the singleton.instanceName: The name of the static instance varible.Args...: Optional constructor arguments.
Example usage:
Q_GLOBAL_STATIC(ConfigManager, configInstance);
class ConfigManager {
public:
static ConfigManager* getInstance() {
return configInstance;
}
};
This avoids manual static initialization, which may not be thread-safe during first access.
Manual Registration Methods
Non-Singleton Types
Use qmlRegisterType<T> to expose instantiable C++ classes to QML:
// C++ side
class DataModel : public QObject {
Q_OBJECT
Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)
public:
explicit DataModel(QObject *parent = nullptr) : QObject(parent) {}
QString title() const { return m_title; }
void setTitle(const QString &t) {
if (m_title != t) {
m_title = t;
emit titleChanged();
}
}
signals:
void titleChanged();
private:
QString m_title;
};
// Registration
qmlRegisterType<DataModel>("org.example.models", 1, 0, "DataModel");
In QML, each declaration creates a new instance:
import org.example.models 1.0
DataModel {
id: modelA
title: "First"
}
DataModel {
id: modelB
title: "Second"
}
Singleton Types
For globally shared instances, use qmlRegisterSingletonType with a factory function:
static QObject* createSingleton(QQmlEngine*, QJSEngine*) {
static NetworkManager instance;
return &instance;
}
// Registration
qmlRegisterSingletonType<NetworkManager>(
"org.example.services", 1, 0, "NetworkManager",
createSingleton
);
Usage in QML:
import org.example.services 1.0
Text {
text: NetworkManager.currentStatus
}
The singleton is shared across all components within the same engine. If the factory always returns the same object, it’s also shared across multiple engines.
Automatic Registration with QML_ELEMENT
Qt’s CMake integration supports automatic registration using macros, reducing boilerplate.
Non-Singleton with QML_ELEMENT
// Counter.h
class Counter : public QObject {
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
public:
int value() const { return m_val; }
void setValue(int v) {
if (m_val != v) {
m_val = v;
emit valueChanged();
}
}
signals:
void valueChanged();
private:
int m_val = 0;
};
CMakeLists.txt:
qt_add_qml_module(MyApp
URI "MyApp"
VERSION 1.0
SOURCES Counter.h Counter.cpp
)
QML usage:
import MyApp 1.0
Counter {
id: counter
onValueChanged: console.log("New value:", value)
}
Singleton with QML_SINGLETON
// Settings.h
class Settings : public QObject {
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
Q_PROPERTY(bool darkMode READ darkMode WRITE setDarkMode NOTIFY darkModeChanged)
public:
bool darkMode() const { return m_dark; }
void setDarkMode(bool d) {
if (m_dark != d) {
m_dark = d;
emit darkModeChanged();
}
}
signals:
void darkModeChanged();
private:
bool m_dark = true;
};
No special CMake changes needed beyond including the file.
QML access:
import MyApp 1.0
Rectangle {
color: Settings.darkMode ? "black" : "white"
}
Exposing Properties and Methods
Property Binding via Q_PROPERTY
Properties must include a READ function, optional WRITE, and a NOTIFY signal for reactive updates:
Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged)
When the C++ property changes and emits the notify signal, QML automatically re-evaluates bindings that depend on it.
Invokable Methods
Mark functions with Q_INVOKABLE to call them directly from QML:
Q_INVOKABLE double calculate(double a, double b) {
return a * b + 10;
}
Slots are also callable from QML without additional macros:
public slots:
void clearCache() {
m_cache.clear();
emit cacheCleared();
}
Signals can be handled in QML using the onSignalName syntax:
Settings {
onDarkModeChanged: updateTheme()
}