> ## Content Index
> Fetch the complete content index at: https://www.smallstepsystems.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Breaking Dependency Cycles in Qt Applications
- URL: https://www.smallstepsystems.com/breaking-dependency-cycles-in-a-qt-application/
- Published: 2020-09-13T14:30:48.000Z
- Updated: 2026-01-25T13:01:21.000Z
- Author: Burkhard Stubert
- Tags: Applications & Services

A fairly common anti-pattern found in Qt applications is to derive a class `MyApplication` from `QApplication` and to have it hand out pointers to a dozen or more major components of the application. Similar to Qt's `qApp` macro, these applications introduce a macro `myApp` pointing to the single `MyApplication` object. The global variable `myApp` provides an easy way to access the major components stored in `MyApplication` from everywhere. This is a recipe for disaster: for spaghetti code with dependency cycles galore. I'll show you several methods how to eliminate these dependency cycles.

## Class-Level Dependency Cycles Galore

The class `MyApplication` inherits `QApplication`. Similar to Qt's `qApp` macro, the single instance of `MyApplication` is globally available through the macro definition

```
#define myApp (static_cast<MyApplication *>(QCoreApplication::instance());
```

We follow the siren call of the global variable `myApp` and make MyApplication hand out pointers to globally used objects like settings, databases, communication gateways and the main HMI components. The declaration of `MyApplication` looks like this:

```
class MyApplication : public QApplication
{
public:
    MySettings *settings() const;

    LanguageDatabase *languageDatabase() const;
    XxxDatabase *xxxDatabase() const;
    ...
    ZzzDatabase *zzzDatabase() const;

    MachineGateway *machineGateway() const;
    CloudGateway *cloudGateway() const;

    HmiComponentA *hmiComponentA() const;
    ...
    HmiComponentF *hmiComponentF() const;
```

`MySettings` is a home-grown implementation of a settings class based on XML files. `LanguageDatabase` is a huge map for translations into different languages. It is backed by an SQL database.

The classes `XxxDatabase`, ..., `ZzzDatabase` store information about the work pieces designed on the operator terminal and produced by the attached machine. These classes are implemented with SQL databases. `MachineGateway` handles the communication between terminal and machine. `CloudGateway` enables the communication between terminal and cloud for OTA updates, remote support or remote diagnosis.

The home screen of the application shows a grid with six buttons. When users click the buttons, the application shows the corresponding components A to F - accessed through the functions `hmiComponentA()` to `hmiComponentF()`.

The interface of `MyApplication` provides global access to objects on very different layers of the application. The functions `hmiComponentA()` to `hmiComponentF()` are used by by the `HomeScreen` class to switch between the main HMI components. The classes `HmiComponentA` to `HmiComponentF` use the gateways, work-piece databases, the language database and the settings directly or indirectly. So, they belong in the top layer of your application. Except for the HomeScreen class, no other class should depend on the main HMI components.

The communication gateways definitely use `MySettings`. The `CloudGateway` may additionaly use the language and work-piece databases directly or indirectly. The `CloudGateway` may use the `MachineGateway`. The gateways must not use the main HMI components. So, the gateways are on a middle layer below the HMI components but above the databases and the settings.

The work-piece databases use `MySettings`. They must not use any other classes provided by `MyApplication`. The work-piece databases are one layer below the gateways.

As `LanguageDatabase` uses `MySettings` for finding out the current language, `MySettings` is on the lowest layer and `LanguageDatabase` one layer above. Note that the settings and the language database can be used by any other class.

The next figure summarises the class dependencies and the application layers. An arrow from class A to class Z says that class A uses class B directly or indirectly (with one or more classes in between).

![](https://storage.ghost.io/c/c7/ee/c7ee1cf5-b86f-491b-aab7-20ad081598e1/content/images/2025/11/dep-cycles-layer.png)

Figure 1: Class dependencies without cycles

So far, I have described the desired structure: multiple layers, no dependency cycles. `MyApplication` spoils this rosy picture. Every arrow in the diagram above means a call through the global variable `myApp`. Let us look at the dependency path

```
HomeScreen -> HmiComponentE -> CloudGateway -> 
    ZzzDatabase -> LanguageDatabase -> MySettings
```

`HomeScreen` uses `myApp->hmiComponentE()` somewhere in its code or in code it uses, `HmiComponentE` uses `myApp->cloudGateway()` somewhere, `CloudGateway` uses `myApp->zzzDatabase` somewhere, and so on. This turns the acyclic dependency graph of Figure 1 into a cyclic graph.

![](https://storage.ghost.io/c/c7/ee/c7ee1cf5-b86f-491b-aab7-20ad081598e1/content/images/2025/11/dep-cycle-layers-2-1.png)

Figure 2: Class dependencies with cycles

Thanks to the global variable `myApp`, every class in the application can call functions from every class in the circle around `MyApplication`. The result is spaghetti code with many dependency cycles.

Dependency cycles prevent us from dividing the application code into self-dependent libraries. If we created a library from the work-piece databases `XxxDatabase`, `YyyDatabase`, `ZzzDatabase` and its auxiliary classes, the libary wouldn't even compile. It lacks the definition of myApp and the declarations of the getter functions in MyApplication.

Adding the header file `my_application.h` to the library gets rid of the compilation errors. However, the linker will pipe up with undefined symbols. We must add the source file `my_application.cpp` to pacify the linker. We must then add the header and source files of all the classes returned by `MyApplication`'s getter functions. These files pull in more and more dependencies. We end up with adding most of the source and header files to the libary. That's quite the opposite of a self-dependent library.

In the rest of this post, I'll show you how to get rid of the dependency cycles. Simply put, I'll transform the cyclic dependency graph of Figure 2 into the acyclic graph of Figure 1.

## Replacing MySettings by QSettings

Every class may need access to the settings. Every class should see the same values for the settings. Hence, the application must store the settings only once - typically in a key-value mapping. The old implementation provides global access through the global variable `myApp`.

```
#include "my_application.h"
#include "my_settings.h"

auto s = myApp->settings();
auto theme = s->value("theme");
s->setValue("theme", "dark");
```

[QSettings](https://doc.qt.io/qt-5/qsettings.html?ref=smallstepsystems.com) hides the complexity of accessing the global key-value mapping behind a simple interface. It is a singleton - without burdening its clients with the downsides of singletons. We create a local `QSettings` variable and can read and write the global value of any given key.

```
#include <QSettings>

QSettings s;
auto theme = s.value("theme");
s.setValue("theme", "dark");
```

The class depends only on `QSettings` from the `Qt5Core` library, which is on a lower layer than any class from the application. The dependency on `MyApplication` is gone. And so is the dependency cycle. As a nice side effect, we get rid of the DIY implementation of `MySettings`, which couldn't be accessed safely from other threads, let alone from other processes.

## Using Qt's i18n Functionality

The current code translates texts as follows:

```
#include "my_application.h"
#include "language_database.h"

auto langDb = myApp->languageDatabase();
m_label->setText(langDb->getTextResources("TXT_LENGTH"));
```

As with the settings, we replace the DIY implementation for internationalisation (i18n) by [Qt's implementation](https://doc.qt.io/qt-5/internationalization.html?ref=smallstepsystems.com).

```
#include <QObject>

m_label->setText(QObject::tr("Length"));
```

As a static function, any class can use the function `QObject::tr()` by including `QObject`. The dependencies on `MyApplication` and `LanguageDatabases` are gone.

## Passing Used Classes to Users' Constructors

The work-piece databases `XxxDatabase`, `YyyDatabase` and `ZzzDatabase` exist once in the application. They are effectively singletons, which are accessed through the singleton `myApp`. We could build a `QSettings`\-like singleton, which looks like an ordinary value class. However, I want to introduce another solution, which demands less effort.

In the old implementation, `HmiComponentA` calls functions of the `XxxDatabase` by calling `myApp->xxxDatabase()->someFunc(...)`. Hence, `HmiComponentA` depends both on `MyApplication` and on `XxxDatabase`. We eliminate the dependeny on `MyApplication` by passing a pointer to `XxxDatabase` to `HmiComponentA`'s constructor. This happens best in the member initialisation list of `MyApplication`'s constructor.

```
// In my_application.h
XxxDatabase m_xxxDatabase;
HmiComponentA m_hmiComponentA;

// In my_application.cpp
MyApplication::MyApplication(int &argc, char **argv)
    : QApplication{argc, argv}
    , m_xxxDatabase{"myXxxDb"}
    , m_hmiComponentA{&m_xxxDatabase}
    ...
```

The constructor of `HmiComponentA` stores the `XxxDatabase` pointer in a member variable so that member functions can use this pointer.

```
// In hmi_component_a.h
class XxxDatabase;
XxxDatabase *m_xxxDatabase;

// In hmi_component_a.cpp
#include "xxx_database.h"
HmiComponentA::HmiComponentA(XxxDatabase *xxxDb, QWidget *parent)
    : QWidget{parent}
    , m_xxxDatabase{xxxDb}
    ...
```

In the old implementation, a member function would query the database as follows:

```
// In hmi_component_a.cpp
#include "my_application.h"
#include "xxx_database.h"

void HmiComponentA::someFunc(const QString &name)
{
     auto xxx = myApp->xxxDatabase()->findXxxThingByName(name);
     ...
```

The new implementation gets rid of the dependency on `MyApplication` and uses the stored `m_xxxDatabase` pointer.

```
// In hmi_component_a.cpp
#include "xxx_database.h"

void HmiComponentA::someFunc(const QString &name)
{
     auto xxx = m_xxxDatabase->findXxxThingByName(name);
     ...
```

If the `XxxDatabase` pointer is not used by `HmiComponentA` itself but by a class `Other` used by `HmiComponentA`, then `HmiComponentA` passes the `XxxDatabase` pointer to `Other`'s constructor. We may have to pass down the `XxxDatabase` pointer through a couple of constructors. A forward declaration of `XxxDatabase` is enough. We should strive to move the construction of `XxxDatabase` as close to its use as possible.

Passing the used objects to the constructors of the using objects has two very useful side effects:

- The dependency relationship between using class and used class is made obvious. For example, the declaration `HmiComponentA(XxxDatabase *xxxDb, ...)` clearly states that `HmiComponentA` depends on `XxxDatabase`. This dependency is hidden when we use global variables or singletons.
- This solution makes unit testing of the using class much easier. We can pass a double (e.g., dummy, fake, mock) of the used class to the constructor in our unit tests. For example, we could pass an in-memory database into the constructor as a double for the SQL-backed database. The unit test doesn't have to pull in all the dependencies of `XxxDatabase`.

If we pass the same classes to constructors over and over again, we can group them together in a class and pass the group class. For example, the three work-piece databases refer to each other, are used together and are on the same abstraction layer. This is a clear sign that we can group them together.

```
class WorkPieceGroup
{
private:
    XxxDatabase m_xxxDb{"myXxxDb"};
    YyyDatabase m_yyyDb{"myYyyDb"};
    ZzzDatabase m_zzzDb{"myZzzDb"};
public:
    XxxDatabase *xxxDatabase() const { return &m_xxxDb; };
    YyyDatabase *yyyDatabase() const { return &m_yyyDb; };
    ZzzDatabase *zzzDatabase() const { return &m_zzzDb; };
};
```

With this grouping, we can simplify the constructor of `HmiComponentA` from

```
HmiComponentA(XxxDatabase *xxxDb, YyyDatabase *yyyDb,
    ZzzDatabase *zzzDb, QWidget parent = nullptr);
```

to

```
HmiComponentA(WorkPieceGroup *wpg, QWidget parent = nullptr);
```

The solution for the work-piece databases also works for the gateways. The same `MachineGateway` object is used by, say, the HMI components B and C to tell the CNC machine how to process a work piece and to display the processing status of the machine and work piece. Component B shows the status in a table, whereas component C uses 2D graphics. As with the work-piece database, `MyApplication` creates the `MachineGateway` object and passes it to the constructors of `HmiComponentB` and `HmiComponentC`.

`CloudGateway` forwards data from the `MachineGateway` to the cloud and data from the cloud to the `MachineGateway`. `CloudGateway` doesn't display any information in the application's HMI. Its information is displayed in applications on remote PC, phones or tablets. Hence, we only pass a `MachineGateway` pointer to `CloudGateway`'s constructor.

## Connecting Components with Signals and Slots

By now we have removed from `MyApplication` the getters for the settings, language database, work-piece databases and gateways - and the cyclic dependencies caused by them. This leaves us with the getters for the six main HMI components.

We can think of the main HMI components as independent applications, which users can start from the `HomeScreen`. Application A cannot simply call a function in application B, because A and B run in different processes. Instead, A sends a message using inter-process communication (IPC) like DBUS or Qt remote objects. B receives the message, processes it and possibly sends a response message back to A.

Qt has adapters for DBUS and remote objects that make sending a message look like emitting a signal and receiving a message like calling a slot. IPC messages are mimicked by signals and slots in Qt applications. If we can use enhanced versions of signals and slots for inter-process communication, we can use the standard version of signals and slots for intra-process communication, that is, for communication between the main HMI components. Let us look at an example.

In HMI component E, users configure which tools are installed on the CNC machine. When users change a tool, the application must notify HMI components B and C about it. Both B and C control the processing of the work piece and show the machine status. The old implementation solves this with direct function calls.

```
// In hmi_component_e.cpp
#include "my_application.h"
#include "hmi_component_b.h"
#include "hmi_component_c.h"

void changeTool(const ToolK &tool)
{
    myApp->hmiComponentB->setTool(tool);
    myApp->hmiComponentC->setTool(tool);
}
```

The new implementation emits or publishes one signal, to which interested components like B and C subscribe. Qt signals and slots implement the [Publish-Subscribe pattern](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe%5Fpattern?ref=smallstepsystems.com).

```
// In hmi_component_e.cpp

void changeTool(const ToolK &tool)
{
    emit toolChanged(tool);
}
```

Components B and C subscribe to the signal in the `MyApplication` constructor with signal-slot connections. We connect the `toolChanged` signal from `HmiComponentE` with the `setTool` slots from `HmiComponentB` and `HmiComponentC`.

```
// In my_application.h
HmiComponentB m_hmiComponentB;
HmiComponentC m_hmiComponentC;
HmiComponentE m_hmiComponentE;

// In my_application.cpp
MyApplication::MyApplication(int &argc, char **argv)
    ...
{
    connect(&m_hmiComponentE, &HmiComponentE::toolChanged,
            &m_hmiComponentB, &HmiComponentB::setTool);
    connect(&m_hmiComponentE, &HmiComponentE::toolChanged,
            &m_hmiComponentC, &HmiComponentC::setTool);
    ...
}
```

Note that `HmiComponentE` does not depend on `MyApplication`, `HmiComponentB` and `HmiComponentC` any more. Using signals and slots breaks the remaining dependency cycles and eliminates the tight coupling between the HMI components B, C and E. In addition to the Publish-Subscribe pattern, signal-slot connections follow the [Mediator pattern](https://en.wikipedia.org/wiki/Mediator%5Fpattern?ref=smallstepsystems.com).

The Mediator pattern has two goals. First, it eliminates tight coupling between components. In our case, it removes the need for an HMI component to include any other HMI component. Second, it enables us to change the communication between objects without changing the objects themselves. We add or remove signal-slot connections between objects.

In all classes of our application, we must replace the calls to functions of any of the main HMI component by signal-slot connections. The offending call may not happen in the main HMI component itselft, but in a child, grandchild or any descendant of the main HMI component. In this case, we pass the signal from the descendant up to the top-level component. The top-level component sends the signal to its peer (another main HMI component). The peer may pass the slot down to the right descendant.

Introducing signal-slot connections between the main HMI components is the last piece in breaking all the dependency cycles caused by the global variable `myApp`. We are ready to apply the final touches.

## Applying the Final Touches

We cannot remove the getter functions for HMI components from `MyApplication` yet. They are needed by `HomeScreen` to switch between the HMI components. We get rid of these getter functions by moving the creation of all the "global" objects from the `MyApplication` constructor to the `HomeScreen` constructor. `HomeScreen` seems to be the natural place, because it allows users to switch between the main HMI components.

Here is a partial implementation of the `HomeScreen` constructor.

```
// In home_screen.h
#include "work_piece_group.h"
#include "machine_gateway.h"
#include "cloud_gateway.h"
#include "hmi_component_b.h"
#include "hmi_component_c.h"
#include "hmi_component_e.h"
...

WorkPieceGroup m_workPieceGroup;  // Holds Xxx, Yyy and Zzz database
MachineGateway m_machineGateway;
CloudGateway m_cloudGateway;
HmiComponentB m_hmiComponentB;
HmiComponentC m_hmiComponentC;
HmiComponentE m_hmiComponentE;
...

// In home_screen.cpp

HomeScreen::HomeScreen(QWidget *parent)
    : QWidget(parent)
    , m_cloudGateway(&m_machineGateway)
    , m_hmiComponentB(&m_workPieceGroup, &m_machineGateway)
    , m_hmiComponentC(&m_workPieceGroup, &m_machineGateway)
    ...
{
    // Connections for signals published by HmiComponentA
    ...

    // Connections for signals published by HmiComponentE
    connect(&m_hmiComponentE, &HmiComponentE::toolChanged,
            &m_hmiComponentB, &HmiComponentB::setTool);
    connect(&m_hmiComponentE, &HmiComponentE::toolChanged,
            &m_hmiComponentC, &HmiComponentC::setTool);
    ...
}
```

`main.cpp` is the only file including `home_screen.h`. The main() function creates an instance of `HomeScreen` and shows it.

We are done! We have removed all the dependency cycles from the application. Now we are free to split up the application code into libraries. The work-piece databases could go into a library, the gateways into another library and each main HMI component into a library of its own.