How to integrate C++ and QML? Registering C++ class as a singleton to QML

Qt Qml development
2022-06-09
8 minutes
Registering C++ class as singleton to QML

Introduction

In ourΒ previous postΒ about integrating C++ and QML, we explained why it is important to integrate QML and C++ and covered the basics of this mechanism. In this article, we will go into more detail and discuss a specific real-life example of integrating elements that can be instantiated one-time only (aka.Β singletons).

 

What is Singleton?

Before we start, let us briefly explain whatΒ singletonΒ is, for those who hear this term for the first time.Β SingletonΒ is a software design pattern that restricts creating an object of a class to a single instance only. There are many use cases forΒ singletons. I.e. an object that holds a program state and can be accessed from different parts of the application.

It is common to utilize this pattern in QML as well. A controller that holds some dynamic (not only) properties like signals and slots and supplies utility functions is a great candidate for aΒ singleton patternΒ class object. However, you should be careful about using this pattern in applications that apply multi-threading.

Qt framework provides multiple ways of registering C++ classes in QML and each of them has its use. QML Javascript can also be used for this purpose. Today we will discuss the most recent one usingΒ qt_add_qml_module(). It was introduced in Qt 6.2 and is widely recommended for registering QML type.

First of all, we need to create a new C++ class we want to expose to QML.

 

#include <QObject>
#include <QtQml/qqml.h>

class ImportantClass : public QObject
{
    Q_OBJECT
    QML_SINGLETON
    QML_ELEMENT
    Q_PROPERTY(int importantNumber READ importantNumber WRITE setImportantNumber NOTIFY importantNumberChanged)

public:
    explicit ImportantClass(QObject *parent = nullptr);

    void setImportantNumber(int num) {
        if (num != m_importantNumber) {
            m_importantNumber = num;
            emit importantNumberChanged();
        }
    }

    int importantNumber() const {
        return m_importantNumber;
    }

public slots:
    double convertFahrenheit(int value) const {
        return value * 1.8 + 32;
    }

signals:
    void importantNumberChanged();

private:
    int m_importantNumber = 10;

};

Everything should look familiar to you, if you have read our previous postΒ in the series. The only difference isΒ the Q_SINGLETONΒ macro, it is important to register a singleton type properly. InΒ ImportantClassΒ we define anΒ importantNumberΒ property and aΒ convertFahrenheit(value)Β function which, as you may guess from the name, converts provided value to Fahrenheit temperature scale.

Next we need to go to ourΒ CMakeLists.txtΒ file and useΒ qt_add_qml_module()Β command to register our class.

 

qt_add_qml_module(appSingletonExample
    URI SingletonExample
    VERSION 1.0
    QML_FILES
        main.qml
    SOURCES
        ImportantClass.h
        ImportantClass.cpp
)

Provide aΒ URI that you would like to use in the QML import statement. Then specify the C++ files your class is in. Congratulations, you have successfully registered a singleton in QML!

Let us create an example to check if everything works correctly. Here is a simple QML application consisting of two buttons.

 

import QtQuick
import QtQuick.Controls

import SingletonExample

Window {
  width: 640
  height: 480
  visible: true
  title: qsTr("Hello World")

  Button {
    id: importantNumberButton

    anchors.centerIn: parent

    width: 100
    height: 50

    text: ImportantClass.importantNumber

    onClicked: {
      ImportantClass.importantNumber += 1
    }
  }

  Button {
    anchors {
      top: importantNumberButton.bottom
      horizontalCenter: importantNumberButton.horizontalCenter
    }

    width: 100
    height: 50

    text: "Click to convert"

    onClicked: {
      text = ImportantClass.convertFahrenheit(ImportantClass.importantNumber)
    }
  }
}

hello world

 

One of them increases the mentioned-aboveΒ importantNumberΒ value and second callsΒ convertFahrenheit()Β function on this value. Qt Creator may sometimes complain about wrong imports, but this is not true. Make sure you update Qt Creator to the newest version or reset the QML type model. Then it is more probable that your own QML modules will be recognized properly in your code editor.

In addition, IDE will not provide autocomplete for registered types. We hope it will be fixed soon, because it does not save you from making subtle spelling mistakes which do not stop the execution of QML code, but result in havingΒ undefinedΒ values.

Next, we need to import the registered type using importΒ URIΒ (import SingletonExample in our case). Now we may useΒ ImportantClassΒ in this QML file: Clicking on the first button increases the value ofΒ ImportantNumberΒ by 1:

 

onClicked: {
  ImportantClass.importantNumber += 1
}

And clicking the second button sets its text to the Fahrenheit value ofΒ importantNumber:

 

onClicked: {
  text = ImportantClass.convertFahrenheit(ImportantClass.importantNumber)
}

Note, that you do not have to initializeΒ ImportantClassΒ objects as it is done internally. Attempts to do that will result in an error. Remember aboutΒ the Q_SINGLETONΒ macro? That is the magic word that extends your QML code with C++ singleton visible as QML object.

That is actually it. You have learned how to register singletons in QML from C++ using CMakeΒ qt_add_qml_module()Β command. If you want to learn about integrating QML and C++ we suggest readingΒ official Qt documentationΒ as well as our other blog posts inΒ β€žHow to integrate QML and C++” series.

 

When to Use Singleton Class?

Trying to decide whether the Singleton pattern is appropriate can be difficult. It can seem like a great idea at first, but it can also lead to unexpected issues later on.

 

The Gang of Four recommends utilizing Singleton when:

There should be precisely one instance of a class accessible from a familiar or client access point.

It should be feasible to broaden the single instance through sub-classing without client code amendment.

However, these points may not provide enough practical guidance for assessing if a Singleton is really suitable for your project. You have to see your circumstances.

 

Benefits of Using Singleton C++ Class

 

Global Access Point

A Singleton can act as a global object and provide access from anywhere within the program. But since it is global, it cannot be modified outside of itself. Thus, it serves as a means to safeguard globals from outside interference.

 

Unique Entity Model

Whenever we model real-life entities through our programs, reasoning about them becomes much easier. For instance, registration offices, global timers, and factories responsible for unique IDs are all examples of Singletons.

This creates a correspondence between programming abstractions and reality which helps in simplifying the understanding of the program for clients and coders alike.

 

Disadvantages

  • Global variables can be inadvertently altered in one place and unintentionally used elsewhere, leading to unexpected results.
  • Singletons create a single object, so multiple references can be created for the same instance. This could lead to the tight coupling of classes that rely on global variables, as a change to the data in one class could have implications for another.
  • Unit testing is hindered by Singletons – If an object and its associated methods are too closely linked, it can be difficult to test without a dedicated class for the Singleton.
  • The use of Singletons can also lead to hidden dependencies- As it is readily available in most code bases, and its reference may not be apparent when passed through different methods.

 

To avoid such issues, it is important to ensure that the class you are creating is exactly a Singleton. Also, when designing this pattern with testing in mind, remember to pass the Singleton as a parameter wherever possible using dependency injection. This will help you identify and resolve issues before it becomes too late.

 

Applications of Singleton C++ Classes to QML

Singleton classes have many potential uses, including cache memory, database connections, drivers, and logging. Here are some major applications:

 

Hardware Interface Access

The usage of singleton classes is dependent on the situation and requirements. These objects are often used to prevent concurrent access to a certain class. This is especially useful in hardware resource utilization limitations, such as for hardware printers where the print spooler can be made a singleton to avoid deadlocks arising from multiple simultaneous accesses.

 

Logger

A logger instance created with a singleton class could also prove beneficial when it comes to log files. If there are multiple clients accessing the same logging utility class, issues may occur regarding concurrent access to the same file due to multiple class instances being created. Utilizing a global point of reference via the singleton pattern would help ensure that no two users simultaneously gain access to this utility.

 

Configuration File

Singleton classes are perfect for configuration files because they improve performance by preventing repeated read accesses from multiple users. Moreover, this creates only one instance of the configuration file, which can be accessed concurrently without any data overload or conflict between different reads by various clients from an application’s end.

The data obtained from this single instance would then be utilized repeatedly across all future calls for better memory management and efficiency.

 

Cache

Finally, caches are another type of service that can benefit from using singleton objects as they provide a global point of reference for their hosting applications, which makes accessing stored information easier and more efficient over time with minimal resources required for the operation.

 

Are Singletons Memory Efficient?

When a C++ class is registered as a singleton to QML, it creates a static instance. This is the most commonly used approach. However, it may not be memory efficient. If the class is not called upon, the Singleton will remain present, and if the Singleton is “heavy,” it can take up unnecessary space in RAM.

 

To Sum It Up

Qt QML and C++ pair is the perfect combination for those looking for a flexible, innovative, and reliable way to develop cross-platform applications. With Qt, you can easily deploy your app across multiple platforms with one codebase, ensuring efficient performance and bug-free coding.

Our team of Qt QML development experts can help you every step of the way – from requirements engineering, actual development, debugging, and migration to deployment. With us on your side, you will never have to worry about making a successful application. Try Scythe Studio services and see how we work – we promise you won’t regret it!

Scythe-Studio - Blog Redactors

Scythe Studio Blog Redactors

NeedΒ QtΒ QML developmentΒ services?

service partner

Let's face it? It is a challenge to get top Qt QML developers on board. Help yourself and start the collaboration with Scythe Studio - real experts in Qt C++ framework.

Discover our capabilities

Latest posts

[ 134 ]