Exposing C++ Classes to QML in Qt

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()
}

Tags: Qt QML C++ Q_PROPERTY QML_ELEMENT

Posted on Thu, 27 Aug 2026 16:43:51 +0000 by Shadeless