Anyone who is not shocked by quantum theory has not understood it.
— Niels Bohr
The Qt object model is built on the principle that everything is an object. In practice, the QObject class is the fundamental base class. The vast majority of Qt classes inherit from it. Any class that uses signals and slots must be derived from QObject.
| Multiple Inheritance
When using multiple inheritance, it is important to remember that |
Listing 2.1. Inheritance order
class MyClass : public QObject, public AnotherClass {
...
};
| More on Multiple Inheritance
Another important rule when using multiple inheritance: only one of the base classes may inherit from |
The QObject class provides built-in support for:
- signals and slots (signal/slot);
- timers;
- object hierarchy composition;
- events and event filtering;
- object hierarchy organization;
- meta-object information;
- type casting;
- properties and property bindings.
Signals and slots are the mechanism that enables efficient communication about events generated by objects. We will cover them in detail later in this chapter.
The timer support built into QObject means that classes derived from it do not need to create a separate timer object, saving development time. Timers are covered in more detail in Chapter 37.
The object hierarchy mechanism significantly reduces development effort by eliminating the need to manually free memory for created objects — parent objects are responsible for destroying their children.
The event filtering mechanism allows events to be intercepted. An event filter can be installed on any class derived from QObject, making it possible to change how an object responds to events without modifying the class source code (see Chapter 15).
Meta-object information includes class inheritance data, which allows you to determine whether one class directly inherits from another and to retrieve the class name.
For type casting, Qt provides the template function qobject_cast(), which is based on meta-information generated by the MOC (see Chapter 3) for classes derived from QObject.
Properties are fields that must have a corresponding read method. They allow external access to object attributes — for example, from the Qt Script scripting language (see Part VII). Properties are also widely used in the Qt Designer visual UI development environment (see Chapter 44), where the mechanism is implemented using preprocessor directives. A property is declared using the Q_PROPERTY macro. The general property definition syntax is as follows:
Q_PROPERTY(type name
READ getFunction
[WRITE setFunction]
[RESET resetFunction]
[NOTIFY notifySignal]
[BINDABLE bindableFunction]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
)
The property type and name are declared first, followed by the name of the read accessor (READ). All remaining parameters are optional. The third parameter specifies the write method (WRITE), the fourth specifies a reset method (RESET), the fifth (NOTIFY) specifies a signal to be emitted when the property changes, the sixth (BINDABLE) specifies a method returning a QBindable object, the seventh (DESIGNABLE) is a boolean indicating whether the property should appear in the Qt Designer property inspector, the eighth (SCRIPTABLE) is a boolean controlling whether the property is accessible from Qt Script, and the ninth (STORED) controls serialization — i.e., whether the property is saved when the object is stored.
Now that you are familiar with the concept of properties (although we will not need this mechanism immediately), let us define a simple example: a property for managing a read-only mode in a class (Listing 2.2).
Listing 2.2. Defining a property to manage read-only mode
class MyClass : public QObject {
Q_OBJECT
Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly NOTIFY readOnlyChanged)
private:
bool m_bReadOnly;
signals:
void readOnlyChanged();
public:
MyClass(QObject* pobj = nullptr) : QObject(pobj)
, m_bReadOnly(false)
{
}
public:
void setReadOnly(bool bReadOnly)
{
if (m_bReadOnly != bReadOnly) {
m_bReadOnly = bReadOnly;
emit readOnlyChanged();
}
}
bool isReadOnly() const
{
return m_bReadOnly;
}
}
The MyClass class shown in Listing 2.2 inherits from QObject. We define the attribute m_bReadOnly to store the state value, which is initialized to false in the constructor. The isReadOnly() and setReadOnly() methods are defined in MyClass to read and modify the attribute value. These methods are registered in the Q_PROPERTY macro. isReadOnly() retrieves the value, so it is specified in the READ section; setReadOnly() modifies it and is listed in the WRITE section. The readOnlyChanged() signal and the NOTIFY parameter pointing to it are also added, enabling property bindings.
From the application code, you can modify the property value as follows:
pobj->setProperty("readOnly", true);
And you can retrieve the current value like this:
bool bReadOnly = pobj->property("readOnly").toBool();
To retrieve all properties of any object along with their values, you can use the following approach. Use the propertyCount() method of QMetaObject to get the number of properties, then iterate over each index to get a property object and call typeName() to get the property type and name() to get the property name. The property value is stored as a QVariant — a universal class capable of holding any type (see Chapter 4) — and is retrieved by calling the property() method on the object itself. Here is an example of how to read all properties of an object:
const QMetaObject* pmo = pobj->metaObject();
for (int i = 0; i < pmo->propertyCount(); ++i) {
const QMetaProperty mp = pmo->property(i);
qDebug() << "Property#:" << i;
qDebug() << "Type:" << mp.typeName();
qDebug() << "Name:" << mp.name();
qDebug() << "Value:" << pobj->property(mp.name());
}
The Signal/Slot Mechanism
GUI widgets respond to user actions in a specific way and send messages. There are several approaches to implementing this behavior.
The classic callback function concept is a mechanism in which a pointer to a function is passed to another function, which will call it under certain conditions. This mechanism forms the foundation of the X Window System and is based on ordinary functions that are called in response to user actions. This approach significantly complicates the source code, making it harder to read. It also lacks type checking for return values, since a pointer to void is returned in all cases. For example, to associate code with a button, a pointer to the button must be passed to the function. When the user clicks the button, the function is called. The libraries themselves do not verify whether the arguments passed to the function are of the expected type, which is a frequent cause of crashes. Another drawback of callback functions is that GUI elements are tightly coupled to the functional parts of the application, making it noticeably harder to develop classes independently. One of the most prominent examples of this approach is the Motif library.
It is important to keep in mind that Motif and the Windows API are designed for procedural programming and will likely encounter difficulties when it comes to implementing object-oriented projects.
There are, however, specialized C++ class libraries that simplify Windows application development. One of the earliest such libraries — and one that, surprisingly, is still in use among a number of individual developers and companies — is Microsoft Foundation Classes (MFC). Calling it object-oriented would be a stretch, since it was created by people who apparently had no familiarity with the most basic principles of object-oriented design. One of the fundamental tenets of object-oriented programming is encapsulation — which forbids leaving class attributes unprotected (since that would allow objects to read and modify data without the knowledge of the owning object) — yet this requirement is violated in many MFC classes. The MFC library itself is a wrapper that provides access to Windows functions implemented in C, which forces developers to occasionally use legacy structures that are at odds with object-oriented principles. It is also worth noting that Microsoft itself does not use MFC for the implementation of the widely known Microsoft Word application.
When using MFC to map messages to handler methods, special macros known as message maps are used (Listing 2.3). These clutter the source code considerably and significantly reduce its readability.
Listing 2.3. A code fragment implemented using MFC
class CPhotoStylerApp : public CWinApp {
public:
CPhotoStylerApp();
public:
virtual BOOL InitInstance();
afx_msg void OnAppAbout();
afx_msg void OnFileNew();
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CPhotoStylerApp, CWinApp)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
ON_COMMAND(ID_FILE_NEW, OnFileNew)
ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP()
Constructs like the one shown in Listing 2.3 are uncomfortable to read and cause confusion during code review. Automated code generation tools exist for producing such boilerplate, but they were created to compensate for poor library design rather than to solve a genuine problem. The library’s own shortcomings force developers to make changes in multiple places for even minor modifications. For example, adding a text field to a dialog requires a whole series of steps: first, creating an attribute in the dialog class to hold the field’s value; second, defining a resource ID for the text field; third, mapping the resource ID and the attribute to each other in DoDataExchange() using DDX_Text(), which enables data exchange between the field and the attribute; and fourth, managing that exchange by passing true or false to UpdateData(). Automated code generation tools can partially address this problem by performing these changes automatically, but they introduce their own drawbacks — such as unnecessary code bloat and potential conflicts with the project’s formatting and naming conventions.
Part of the blame lies with C++ itself. The language was not designed for writing user interfaces and therefore does not provide built-in support that would make UI programming more convenient. If event dispatching were handled natively by the language, there would be no need for these kinds of macros. This gap is precisely what made the arrival of the Qt library such a breakthrough — unlike most other programming libraries, Qt extends C++ with additional keywords specifically designed for this task.
Qt solves the problem of extending C++ through a special preprocessor called MOC (Meta Object Compiler). It scans classes for the Q_OBJECT macro in their definitions and generates all the necessary additional information into a separate file, entirely without developer involvement. This automatic code generation does not conflict with the normal C++ development workflow — after all, the standard preprocessor also generates intermediate code before the program is compiled. MOC works similarly, writing all required additional information into a separate file whose contents the developer never needs to examine. The Q_OBJECT macro must appear on the line immediately following the class keyword and the class name declaration. It is critically important to remember that a semicolon must not follow the macro. The macro should be included in a class definition whenever the class uses the signal/slot mechanism or requires property information.
| Note
Classes containing the |
The signal/slot mechanism completely replaces the old callback function model. It is highly flexible and fully object-oriented. Signals and slots are the cornerstone concept of Qt programming, enabling unrelated objects to communicate with each other. Every class derived from QObject is capable of sending and receiving signals. Consider a simple real-world analogy: your phone rings and you pick it up. In the language of signals and slots, this can be described as: the “phone” object emitted the “ring” signal, to which the “person” object responded with the “answer” slot.
Using the signal/slot mechanism gives the developer the following advantages:
- every class derived from
QObjectcan have any number of signals and slots; - messages sent through signals can have multiple arguments of any type;
- a signal can be connected to multiple slots — the emitted signal will be delivered to all connected slots;
- a slot can receive messages from multiple signals belonging to different objects — this simply requires creating multiple connections using the
connect()method with the same target slot. TheQSignalMapperclass is used in special cases where different data from different signal sources must be forwarded to a single slot without creating additional subclasses; - signal/slot connections can be made at any point in the application;
- signals and slots are mechanisms for communication between objects — moreover, this communication can occur between objects residing in different threads (see Chapter 38);
- when an object is destroyed, all of its signal/slot connections are automatically disconnected. This guarantees that signals will not be sent to objects that no longer exist.
It is also worth noting the drawbacks of using signals and slots:
- signals and slots are not part of the C++ language, so an additional preprocessor must run before the program is compiled;
- emitting a signal is slightly slower than a direct function call as used in the callback function model;
- class
QObjectmust be inherited; - when using the traditional syntax, no compile-time checks are performed to verify whether a signal or slot exists in the respective classes, whether their signatures are compatible, or whether they can be connected. Errors are only reported at runtime in the debugger or on the console. All such information is sent to the standard output, so on Windows you need to add the
consoleoption to theCONFIGsection in the project file (see Chapter 3) to see it — no additional project file changes are required on macOS or Linux.
| Alternative Signal/Slot Connection Syntax
Using the modern signal/slot connection syntax eliminates the above drawback by allowing connection errors to be detected at compile time. In Qt6, this syntax is the preferred approach. See the “Connecting Objects” section later in this chapter for details. |
Signals
Signals are all around us in everyday life: an alarm clock going off, a traffic officer’s hand gesture, a flare sent up in an emergency. In Qt programming, the term “signal” refers to methods that are capable of transmitting messages. A signal may be triggered by a notification of a state change in a control element — for example, a slider being moved. A connected object monitoring such signals can respond accordingly, though it is not required to do so. This is a key point: the connected objects can be entirely independent and implemented separately from each other. This design allows the object sending signals to have no concern about what happens with them afterward. The sending object may not even be aware that any other objects are receiving and processing its signals. This decoupling makes it possible to decompose a large project into components that different developers can work on independently, later wiring them together using signals and slots.
Signals are defined in a class like ordinary methods, except they have no implementation. From the developer’s perspective, they are nothing more than method prototypes declared in the class header file. MOC takes full responsibility for providing the implementation of these methods. Signal methods must not return any values, so the return type before the method name must always be void.
A signal does not have to be connected to a slot. If no connection exists, the signal is simply not handled. This separation between the sending and receiving objects ensures that no connected slot can interfere with the object that emitted the signal.
The library provides a large number of ready-made signals for existing controls. In most cases, these built-in signals are sufficient, but sometimes you need to define new signals in your own classes. An example of a signal definition is shown in Listing 2.4.
Listing 2.4. Defining a signal
class MySignal {
Q_OBJECT
...
signals:
void doIt();
...
};
Note the signal method doIt(). It has no implementation — that responsibility is taken over by MOC, which generates an implementation roughly like this:
void MySignal::doIt()
{
QMetaObject::activate(this, &staticMetaObject, 0, 0);
}
Signals are declared using the special signals: section and by default have protected access. This means signals can only be emitted from within the class itself or its subclasses using the emit keyword. This mechanism prevents a signal from being triggered outside of the object, which aligns with Qt’s architectural principles and ensures correct signal/slot behavior. For testing or special-purpose use cases, it is recommended to use additional accessor methods or friend classes rather than making signals public.
A signal is emitted using the emit keyword. Since signals act as method calls, the statement emit doIt() is equivalent to a regular call to doIt(). Signals can only be emitted from within the classes that define them. For example, in Listing 2.4, the signal doIt() can only be emitted by objects of class MySignal and no others. To allow emitting the signal programmatically from an object of that class, you can add a sendSignal() method whose call causes the MySignal object to emit doIt(), as shown in Listing 2.5.
Listing 2.5. Implementing a signal
class MySignal {
Q_OBJECT
public:
void sendSignal()
{
emit doIt();
}
signals:
void doIt();
};
Signals can also carry information through parameters. For example, if you need to pass a text string in a signal, you can implement it as shown in Listing 2.6.
Listing 2.6. Implementing a signal with a parameter
class MySignal : public QObject {
Q_OBJECT
public:
void sendSignal()
{
emit sendString("Information");
}
signals:
void sendString(const QString&);
};
Slots
Slots are methods that are connected to signals. Essentially, they are ordinary methods. The primary distinction between slots and regular methods is the ability of slots to receive signals. Like regular methods, slots are defined in a class as public, private, or protected. If a slot should only be connectable to signals from external objects and not callable as a regular method from the outside, declare it as protected or private. In all other cases, declare slots as public. Each group of slot declarations must be preceded by the appropriate access specifier: private slots:, protected slots:, or public slots:. Slots can also be virtual.
| Connecting a Signal to a Virtual Slot
Connecting a signal to a virtual slot is approximately ten times slower than connecting to a non-virtual slot. Therefore, do not make slots virtual unless there is a specific reason to do so. |
There are also a few minor restrictions that distinguish slots from regular methods. Default parameter values cannot be used in slots (e.g., slotMethod(int n = 8)), and slots cannot be declared as static.
The library provides a wide range of ready-made slots. However, defining slots for your own classes is a common task. An example slot implementation is shown in Listing 2.7.
Listing 2.7. Implementing a slot
class MySlot : public QObject {
Q_OBJECT
public:
MySlot();
public slots:
void slot()
{
qDebug() << "I'm a slot";
}
};
Inside a slot, you can call the sender() method to determine which object emitted the signal. The method returns a pointer to an object of type QObject. For example, the following code prints the name of the object that emitted the signal to the console:
void slot()
{
qDebug() << sender()->objectName();
}
Connecting Objects
Objects are connected using the static connect() method defined in the QObject class. Qt6 recommends using the new connection syntax based on member function pointers, but the older SIGNAL/SLOT macro syntax is still supported for backward compatibility.
New Connection Syntax
The new connection syntax looks like this:
QObject::connect(sender, &SenderClass::signalMethod,
receiver, &ReceiverClass::slotMethod);
The advantage of this syntax is that type checking and method existence are verified at compile time rather than at runtime. This allows errors to be caught much earlier. Here is an example:
auto* pcmd = new QPushButton("Click me");
QObject::connect(pcmd, &QPushButton::clicked,
this, &MyClass::handleButtonClick);
You can also use lambda expressions as a slot:
QObject::connect(pcmd, &QPushButton::clicked,
[=]() { qDebug() << "Button clicked!"; });
Advantages of the modern connect() syntax:
- compile-time type checking and method existence verification;
- IDE auto-completion support (method can be selected from a dropdown);
- better performance;
- support for lambda expressions.
Potential drawbacks:
- method signatures are less explicitly visible in code, which may reduce readability;
- complications arise with overloaded methods (requires the use of
qOverload()); - some verbosity when working with member function pointers.
Classic Connection Syntax
The classic connection syntax using the SIGNAL and SLOT macros looks like this:
QObject::connect(const QObject* sender,
const char* signal,
const QObject* receiver,
const char* slot,
Qt::ConnectionType type = Qt::AutoConnection
);
It accepts the following five parameters:
sender— a pointer to the object emitting the signal;signal— the signal to connect to. The prototype (name and arguments) of the signal method must be wrapped in theSIGNAL(method())macro;receiver— a pointer to the object containing the slot for handling the signal;slot— the slot to be called when the signal is received. The slot prototype must be wrapped in theSLOT(method())macro;type— controls the connection mode. Possible values include:Qt::DirectConnection— the signal is handled immediately by calling the corresponding slot method;Qt::QueuedConnection— the signal is converted to an event (see Chapter 14) and placed in the common event queue for processing;Qt::BlockingQueuedConnection— similar toQt::QueuedConnection, but blocks the sender until the slot has been invoked;Qt::UniqueConnection— can be combined with the above types via bitwise OR; prevents duplicate connections;Qt::AutoConnection— the automatic mode: if the object emitting the signal resides in the same thread as the receiving object,Qt::DirectConnectionis used; otherwise,Qt::QueuedConnectionis used. This is the default for theconnect()method.
The following example demonstrates how object connections can be established using the classic connect() syntax:
void main()
{
...
QObject::connect(pSender, SIGNAL(signalMethod()),
pReceiver, SLOT(slotMethod())
);
...
}
| Note
Although the old |
If the call is made from a class derived from QObject, the QObject:: prefix can be omitted:
MyClass::MyClass() : QObject()
{
...
connect(pSender, &SenderClass::signalMethod,
pReceiver, &ReceiverClass::slotMethod
);
...
}
If the slot belongs to the class where the connection is being established, you can use the shorthand form of connect() with this as the receiver:
MyClass::MyClass() : QObject()
{
connect(pSender, &SenderClass::signalMethod, this, &MyClass::slot);
}
void MyClass::slot()
{
qDebug() << "I'm a slot";
}
The connect() method returns a Connection object that can be used to determine whether the connection was established successfully. You can use this, for example, to trigger a fatal assertion via the Q_ASSERT() macro if an error occurs in the method.
The Connection class provides an implicit conversion operator to bool, so we use a variable of that type in the example. The code might look like this:
bool bOk = true;
bOk &= connect(pcmd1, &QPushButton::clicked, pobjReceiver1, &Receiver::slotButton1Clicked);
bOk &= connect(pcmd2, &QPushButton::clicked, pobjReceiver2, &Receiver::slotButton2Clicked);
Q_ASSERT(bOk);
There are situations where an object does not process a signal but simply forwards it. In such cases, you do not need to define a slot that re-emits the signal using emit. You can simply connect signals to each other. The forwarded signal must be declared in the class definition:
MyClass::MyClass() : QObject()
{
connect(pSender, SIGNAL(signalMethod()), SIGNAL(mySignal()));
}
Signal emission can be temporarily blocked by calling blockSignals() with a true argument. The object will remain silent until the block is released by calling the same method with false. The current blocking state can be checked using signalsBlocked().
| Connecting a Signal Directly to a Lambda Function
In Qt6, you can connect a signal directly to a lambda function, which simplifies signal handling without the need to define a separate slot. The following connection will hide the widget |
The application windows shown in Fig. 2.1 demonstrate the signal/slot mechanism in action. For this example, an application is created (Listings 2.8–2.10) with one window containing a push button (Fig. 2.1, right) and another window containing a label widget (Fig. 2.1, left). Clicking the ADD button in the right window increments the value displayed in the left window by one. Once the value reaches five, the application exits.

Listing 2.8. Main application file (main.cpp)
#include <QApplication>
#include <QLabel>
#include <QPushButton>
#include "Counter.h"
int main (int argc, char** argv)
{
QApplication app(argc, argv);
QLabel lbl("0");
QPushButton cmd("ADD");
Counter counter;
lbl.show();
cmd.show();
QObject::connect(&cmd, &QPushButton::clicked,
&counter, &Counter::slotInc
);
QObject::connect(&counter, &Counter::counterChanged,
&lbl, qOverload<int>(&QLabel::setNum)
);
QObject::connect(&counter, &Counter::goodbye,
&app, &QApplication::quit
);
return app.exec();
}
In the main application file (Listing 2.8), a label object lbl, a push button cmd, and a counter object counter are created (described in Listings 2.9–2.10). The clicked() signal is then connected to the slotInc() slot. Each time the button is pressed, slotInc() is called, incrementing the counter value by 1. The counter must be able to report these changes so that the label widget always displays the current value. To accomplish this, the counterChanged(int) signal — which passes the current counter value as a parameter — is connected to the setNum(int) slot, which can accept that value.
| Note
In Qt6, for overloaded methods such as |
| Watch for matching signal and slot types!
When connecting signals and slots that pass values, it is important to ensure that their types match. For example, a signal that passes an
|
| A slot can ignore values passed by a signal
It is also important to understand that a slot can ignore values passed by a signal. For example, you can connect a signal that emits an
|
| Do not include the variable name together with the type!
Note that only the type
|
Finally, the goodbye() signal — indicating that the counter has finished — is connected to the application object’s quit() slot, which terminates the application after the ADD button is pressed for the fifth time.
Listing 2.9. Counter class (Counter.h)
#pragma once
#include <QObject>
// ======================================================================
class Counter : public QObject {
Q_OBJECT
private:
int m_nValue;
public:
Counter();
public slots:
void slotInc();
signals:
void goodbye ( );
void counterChanged(int);
};
As shown in Listing 2.9, the counter class definition contains two signals: goodbye(), which indicates that the counter has finished, and counterChanged(int), which carries the current counter value; as well as a slot slotInc(), which increments the counter value by one.
Listing 2.10. Counter class (counter.cpp)
#include "Counter.h"
// ----------------------------------------------------------------------
Counter::Counter() : QObject()
, m_nValue(0)
{
}
// ----------------------------------------------------------------------
void Counter::slotInc()
{
emit counterChanged(++m_nValue);
if (m_nValue == 5) {
emit goodbye();
}
}
In Listing 2.10, the slotInc() slot emits two signals: counterChanged() and goodbye(). The goodbye() signal is emitted when the m_nValue attribute equals 5.
A slot with no parameters can be connected to a signal that has parameters. This is useful when a signal provides more information than the receiving object requires. In such cases, the slot can simply omit the parameters:
MyClass::MyClass() : QObject()
{
connect(pSender, &SenderClass::signalMethod, this, &MyClass::mySignal);
}
If you are unsure whether a signal’s parameter will be needed in the future, it is better to define the slot with the parameter and simply ignore it inside the slot. That way, if the need arises later, you will not have to change the slot’s prototype.
Disconnecting Objects
If objects can be connected, they must also be disconnectable. In Qt, when an object is destroyed, all of its connections are automatically removed. However, in rare cases you may need to remove these connections manually. The static disconnect() method is available for this purpose, and its parameters are analogous to those of the static connect() method. For the modern connection syntax:
QObject::disconnect(sender, &SenderClass::signalMethod,
receiver, &ReceiverClass::slotMethod);
For the classic SIGNAL() and SLOT() syntax:
QObject::disconnect(sender, SIGNAL(signalMethod()),
receiver, SLOT(slotMethod()));
The following example demonstrates how disconnecting objects can be performed in an application:
void main()
{
...
QObject::disconnect(pSender, SIGNAL(signalMethod()),
pReceiver, SLOT(slotMethod())
);
...
}
There are also shorthand forms of the disconnect() method:
- disconnect all connections from a specific signal:
sender->disconnect(SIGNAL(signalMethod())); - disconnect all connections to a specific receiver object:
sender->disconnect(receiver); - disconnect all connections:
sender->disconnect();
Signal Remapping
If you want to reduce the number of slot methods in a class and handle different signals in a single slot, you can use the QSignalMapper class. It allows you to remap signals and route values of types int, QString, or QWidget to a single slot. Let us walk through this mechanism using QString values as an example. Suppose the application has two buttons: when the first button is clicked, the message “Button1 Action” should be displayed, and when the second is clicked, “Button2 Action” should appear. We could implement two separate slots, each connected to the clicked() signal of one button, each printing its own message. But instead, we will use the QSignalMapper class (Listing 2.11).
Listing 2.11. Signal remapping example (classic approach)
MyClass::MyClass(QWidget* pwgt)
{
...
auto* psigMapper = new QSignalMapper(this);
connect(psigMapper, SIGNAL(mapped(const QString&)),
this, SLOT(slotShowAction(const QString&))
);
auto* pcmd1 = new QPushButton("Button1");
connect(pcmd1, SIGNAL(clicked()), psigMapper, SLOT(map()));
psigMapper->setMapping(pcmd1, "Button1 Action");
auto* pcmd2 = new QPushButton("Button2");
connect(pcmd2, SIGNAL(clicked()), psigMapper, SLOT(map()));
psigMapper->setMapping(pcmd2, "Button2 Action");
...
}
void MyClass::slotShowAction(const QString& str)
{
qDebug() << str;
}
In Listing 2.11, we create a QSignalMapper object and connect its mapped() signal to the single slotShowAction() slot, which accepts QString objects. The QSignalMapper class provides a map() slot that each object whose signal needs to be remapped must connect to. Using QSignalMapper::setMapping(), we associate a specific value to be forwarded to the slot when a signal is received — in our case, the clicked() signal.
That’s all there is to it. It is worth noting, however, that signal remapping should not be overused, as excessive use of these constructs can noticeably reduce code readability.
In Qt6, the QSignalMapper class is largely obsolete thanks to lambda expression support. Nonetheless, it may still be useful for maintaining compatibility with older code. Listing 2.12 shows how the example from Listing 2.11 would look using lambda expressions (the modern Qt6 approach).
Listing 2.12. Signal remapping example (modern approach)
MyClass::MyClass(QWidget* pwgt)
{
…
auto* pcmd1 = new QPushButton("Button1");
connect(pcmd1, &QPushButton::clicked, [=]() {
slotShowAction("Button1 Action");
});
auto* pcmd2 = new QPushButton("Button2");
connect(pcmd2, &QPushButton::clicked, [=]() {
slotShowAction("Button2 Action");
});
…
}
void MyClass::slotShowAction(const QString& str)
{
qDebug() << str;
}
Organizing Object Hierarchies
Organizing objects into hierarchies relieves the developer from having to manually manage memory deallocation for created objects.
The constructor of the QObject class looks like this:
QObject(QObject* pobj = nullptr);
Its parameter is a pointer to another object of class QObject or of a class derived from it. This parameter makes it possible to create object hierarchies, as it represents a pointer to the parent object. If a null value is passed — or nothing is passed at all — it means the object has no parent and will be a top-level object at the root of the object hierarchy. A parent object is specified in the constructor at the time the object is created, but it can be changed at any point during program execution using the setParent() method.
Newly created objects have no name by default. A name can be assigned using the setObjectName() method. Object names are not critical, but they can be helpful during debugging. To retrieve an object’s name, call the objectName() method, as shown in Listing 2.13.
Listing 2.13. Creating an object hierarchy
auto* pobj1 = new QObject;
auto* pobj2 = new QObject(pobj1);
auto* pobj4 = new QObject(pobj2);
auto* pobj3 = new QObject(pobj1);
pobj2->setObjectName("the first child of pobj1");
pobj3->setObjectName("the second child of pobj1");
pobj4->setObjectName("the first child of pobj2");
In the first line of Listing 2.13, a top-level object (an object without a parent) is created. When pobj2 is created, a pointer to pobj1 is passed as the parent argument to its constructor. Object pobj3 has pobj1 as its parent, while pobj4 has pobj2 as its parent. The resulting object hierarchy is shown in Fig. 2.2.
When a created object is destroyed (when its destructor is called), all child objects attached to it are destroyed automatically. This recursive destruction behavior significantly simplifies programming, since there is no need to manually free memory resources. This is precisely why objects — especially non-top-level objects — should be created dynamically using the new operator; otherwise, destroying an object will result in a runtime error.

| All objects must be created dynamically in memory!
One of the most common mistakes made by C++ developers new to Qt is manually controlling memory allocation and deallocation, or creating controls non-dynamically. When programming with Qt, it is important to remember that objects with a parent must be created dynamically using the |
Two methods are available for querying the object hierarchy: parent() and children(). The parent() method returns the parent object. According to Fig. 2.2, a call to pobj2->parent() will return a pointer to obj1. For top-level objects, this method returns nullptr. To print the names of all ancestor objects for a given object to the console, you can use the approach shown in Listing 2.14 (demonstrated here for object pobj4 from Listing 2.13).
Listing 2.14. Printing ancestor object names
for (QObject* pobj = pobj4; pobj; pobj = pobj->parent()) {
qDebug() << pobj->objectName();
}
The console output will be:
the first child of pobj2
the first child of pobj1
The children() method returns a constant pointer to the list of child objects. For the earlier example (see Listing 2.13 and Fig. 2.2), calling pobj1->children() returns a pointer to a QObjectList containing two elements: the pointers pobj2 and pobj3.
You can search for a specific child object using the findChild() method. Pass the target object’s name as the parameter. For example, the following call returns a pointer to pobj4:
QObject* pobj = pobj1->findChild<QObject*>("the first child of pobj2");
For extended searches, the findChildren() method is available, returning a list of object pointers. All of its parameters are optional — you can pass either a name string or a regular expression (see Chapter 4), and calling the method with no arguments will return a list of pointers to all child objects. The search is performed recursively. The following call returns a list of pointers to all objects whose names begin with “th” — in our case, there are three such objects:
QList<QObject*> plist = pobj1->findChildren<QObject*>(QRegExp("th*"));
To return all child objects of a specific type regardless of their names, simply omit the argument:
QList<QObject*> plist = pobj1->findChildren<QObject*>();
In our particular example, this function returns pointers to objects pobj2, pobj3, and pobj4.
For debugging purposes, the dumpObjectInfo() method is useful. It outputs the following information about an object:
- the object’s name;
- the class from which the object was created;
- signal/slot connections.
All of this information is written to the standard output stream stdout.
During debugging, you can also use the dumpObjectTree() method, which displays child objects as a hierarchy. For example, calling dumpObjectTree() on our pobj1 object from Listing 2.13 produces:
QObject::
QObject::the first child of pobj1
QObject::the first child of pobj2
QObject::the second child of pobj1
Meta-Object Information
Every object created from QObject or a class derived from it has a data structure called meta-object information (the QMetaObject class). It stores information about signals, slots (including pointers to them), the class itself, and its inheritance. This information is accessible via the QObject::metaObject() method. For example, to retrieve the class name of the object that created it, you can do the following:
qDebug() << pobj1->metaObject()->className();
To compare the class name against a known value:
if (pobj1->metaObject()->className() == "MyClass") {
// Perform some actions
}
For querying class inheritance, the inherits(const char*) method is available. It is defined directly in QObject and returns true if the object’s class is derived from — or is the same as — the class specified in the method argument; otherwise it returns false. For example:
if (pobj->inherits("QWidget")) {
auto* pwgt = static_cast<QWidget*>(pobj);
// Perform some actions with pwgt
}
The qobject_cast type casting operation also makes use of meta-object information. Using inherits(), the example above can be rewritten as:
auto* pwgt = qobject_cast<QWidget*>(pobj);
if (pwgt) {
// Perform some actions with pwgt
}
The tr() method is also part of the meta-object information and is used for application internationalization. Internationalization is described in detail in Chapter 30.
Summary
In this chapter, we explored the essence of signals and slots — a worthy alternative to the previously common mechanisms for implementing communication between GUI components, one that significantly improves source code readability.
Signals and slots can be connected to each other, and a single signal can be connected to multiple slots. Conversely, a slot can be connected to multiple signals. When a slot does nothing more than re-emit the received signal, you can skip the slot entirely and connect signals to each other directly. Signal methods must be declared in the class definition using the special signals keyword, while slots use the slots keyword. Slots are ordinary C++ methods and may carry the public, protected, or private access modifier. The implementation of signal code is handled by MOC. Signals are emitted using the emit keyword. Any class containing signals and slots must inherit from QObject or from a class that does. Signal/slot connections can always be removed (disconnected) using the disconnect() method, though this is rarely necessary since all connections are automatically destroyed when the object is deleted. Connections are established using the static QObject::connect() method.
QObject is, in essence, the fundamental class in Qt programming. The QObject constructor takes a single parameter — a pointer to the parent object:
QObject(QObject* pobj = nullptr);
Object names are assigned using the separate setObjectName() method. Object properties are important because they allow you to query information about the class and the object at runtime. Objects that have a parent must be created dynamically using the new operator. Exceptions may include top-level objects (those without parents) or temporary objects in the main() function that exist for the full duration of the program.
Because the signal/slot concept and inheritance information could not be implemented using C++ alone, a special preprocessor was created — MOC (Meta Object Compiler). Its task is to generate additional CPP files for header files; these files are compiled and their object code is linked into the program’s executable. For MOC to recognize classes that require this processing, such a class must contain the Q_OBJECT macro.
Qt6 introduces a modern signal/slot connection syntax using member function pointers and lambda expressions, which is the preferred approach for creating connections.
Unlock the full potential of this chapter! Access the supplementary materials prepared for it, ask a question, share your experience, or join the discussion at: https://qt-book.com/02-69-en/
