Value Object and QObject
The Meta-Object system
Qt's meta-object system provides the signals and slots mechanism for inter-object communication, run-time type information (RTTI), and the dynamic property system. Signals and slots is one of the most important concepts in Qt, and it will be discussed in the next chapter.
The meta-object system is implemented with a three-part mechanism:
QObject
is the base class that all objects in the meta-object system inherit from.- The
Q_OBJECT
macro is used to enable meta-object features when declared within a class definition. - The Meta-Object Compiler (
moc
) will read the class definitions with the declaredQ_OBJECT
macro and produce the meta-object code.
Value Type and Identity Type
Value types can be copied and assigned. Many of the Qt value types, such as QString
and Qt Containers, also use implicit sharing (copy-on-write). Implicitly shared classes are both safe and efficient when passed as arguments, because only a pointer to the data is passed around, and the data is copied only if and when a function writes to it. Custom value types can be made known to the meta-object system by using the Q_DECLARE_METATYPE
macro, which makes them storable in QVariant
. This is useful when, for example, reading properties. How this is done will be discussed later in the chapter, with the property system in general.
Identity type in turn derives from QObject
. It extends C++ with many dynamic features using a meta-object system. QObject
has neither a copy constructor nor an assignment operator; this is by design. Actually, they are declared, but in a private section with the macro Q_DISABLE_COPY()
. In fact, all Qt classes derived from QObject
(direct or indirect) use this macro to declare their copy constructor and assignment operator to be private. The main consequence is that you should use pointers to QObject
(or to your QObject
subclass) where you might otherwise be tempted to use your QObject
subclass as a value. For example, without a copy constructor, you can't use a subclass of QObject
as the value to be stored in one of the container classes. You must store pointers.
The Qt Object Model and the QObject class
The QObject
class is the base class of all Qt objects. It is the heart of the Qt Object Model. The central feature in this model is a very powerful mechanism for seamless object communication called signals and slots. The signals and slots system will be discussed at length in the next chapter.
QObject
s organize themselves in object trees. When you create a QObject
with another object as parent, the object will automatically add itself to the parent's children()
list. The parent takes ownership of the object; i.e., it will automatically delete its children in its destructor. You can look for an object by name and optionally type using findChild()
or findChildren()
. The parent-child relationship will be discussed in chapter 2.03.
Every object has an objectName()
and its class name can be found via the corresponding metaObject()
(see QMetaObject::className()
). You can determine whether the object's class inherits another class in the QObject
inheritance hierarchy by using the inherits()
function.
When an object is deleted, it emits a destroyed()
signal. You can catch this signal to avoid dangling references to QObject
s.
Last but not least, QObject
provides the basic timer support in Qt; see QTimer
for high-level support for timers.
The Q_OBJECT macro
The Q_OBJECT
macro must appear in the private section of a class definition that declares its own signals and slots or that uses other services provided by Qt's meta-object system.
The moc
tool reads a C++ header file. If it finds one or more class declarations that contain the Q_OBJECT
macro, it produces a C++ source file containing the meta-object code for those classes. This meta-object code implements the underlying functionality needed for the runtime features. When using qmake
you don't have to manually run the moc
tool, it will be done automatically.
Example:
#include <QObject>
class Counter : public QObject
{
Q_OBJECT
public:
Counter() { m_value = 0; }
int value() const { return m_value; }
public slots:
void setValue(int value);
signals:
void valueChanged(int newValue);
private:
int m_value;
};
This macro requires the class to be a subclass of QObject
. You can use Q_GADGET
instead of Q_OBJECT
to enable the meta object system's support for enums in a class that is not a QObject
subclass. The Q_GADGET
macro is a lighter version of the Q_OBJECT
macro for classes that do not inherit from QObject
but still want to use some of the reflection capabilities offered by QMetaObject
. Just like the Q_OBJECT
macro, it must appear in the private section of a class definition.
Notice that the Q_OBJECT
macro is mandatory for any object that implements signals, slots or properties. We strongly recommend the use of this macro in all subclasses of QObject
regardless of whether or not they actually use signals, slots and properties, since failure to do so may lead certain functions to exhibit strange behavior.
Internationalization (I18n)
All QObject
subclasses support Qt's translation features, making it possible to translate an application's user interface into different languages.
To make user-visible text translatable, it must be wrapped in calls to the tr()
function. This is explained in detail in the Writing Source Code for Translation document. We won't be covering internationalization on this course, but it's good to know it's there.
The Property System
Qt provides a sophisticated property system similar to the ones supplied by some compiler vendors. However, as a compiler- and platform-independent library, Qt does not rely on non-standard compiler features like __property
or [property]
. The Qt solution works with any standard C++ compiler on every platform Qt supports. It is based on the Meta-Object System that also provides the signals and slots system.
Requirements for Declaring Properties
To declare a property, use the Q_PROPERTY()
macro in a class that inherits QObject
. Here is an example showing how to export member variables as Qt properties using the MEMBER
keyword. Note that a NOTIFY
signal must be specified to allow QML property bindings. We will talk more about QML in Part 3!
Q_PROPERTY(QColor color MEMBER m_color NOTIFY colorChanged)
Q_PROPERTY(qreal spacing MEMBER m_spacing NOTIFY spacingChanged)
Q_PROPERTY(QString text MEMBER m_text NOTIFY textChanged)
...
signals:
void colorChanged();
void spacingChanged();
void textChanged(const QString &newText);
private:
QColor m_color;
qreal m_spacing;
QString m_text;
A property behaves like a class data member, but it has additional features accessible through the Meta-Object System
The property declaration syntax has multiple keywords to specify the behaviour of the declared property. Here are the most relevant ones:
READ
- For reading the property value. Ideally a const function is used for this purpose, and it must return either the property's type or a const reference to that type. eg.,QWidget::focus
is a read-only property with READ function,QWidget::hasFocus()
. Required if noMEMBER
variable was specified.WRITE
- For setting the property value. It must return void and must take exactly one argument, either of the property's type or a pointer or reference to that type. e.g.,QWidget::enabled
has theWRITE
functionQWidget::setEnabled()
. Read-only properties do not needWRITE
functions. e.g.,QWidget::focus
has noWRITE
function.MEMBER
- Required if noREAD
accessor function is specified. This makes the given member variable readable and writable without the need of creatingREAD
andWRITE
accessor functions. It's still possible to useREAD
orWRITE
accessor functions in addition toMEMBER
variable association (but not both), if you need to control the variable access.RESET
- Optional. For setting the property back to its context specific default value. e.g.,QWidget::cursor
has the typicalREAD
andWRITE functions
,QWidget::cursor()
andQWidget::setCursor()
, and it also has aRESET
function,QWidget::unsetCursor()
, since no call toQWidget::setCursor()
can mean reset to the context specific cursor. TheRESET
function must return void and take no parameters.NOTIFY
- Optional. If defined, it should specify one existing signal in that class that is emitted whenever the value of the property changes.NOTIFY
signals forMEMBER
variables must take zero or one parameter, which must be of the same type as the property. The parameter will take the new value of the property. TheNOTIFY
signal should only be emitted when the property has really been changed, to avoid bindings being unnecessarily re-evaluated in QML, for example. Qt emits automatically that signal when needed forMEMBER
properties that do not have an explicit setter.USER
- Attribute indicates whether the property is designated as the user-facing or user-editable property for the class. Normally, there is only oneUSER
property per class (default false). e.g.,QAbstractButton::checked
is the user editable property for (checkable) buttons.
The property type can be any type supported by QVariant
, or it can be a user-defined type. In this example, class QDate
is considered to be a user-defined type.
Q_PROPERTY(QDate date READ getDate WRITE setDate)
Because QDate
is user-defined, you must include the <QDate>
header file with the property declaration.
For historical reasons, QMap
and QList
as property types are synonym of QVariantMap
and QVariantList
.
Reading and Writing Properties
A property can be read and written using the generic functions QObject::property()
and QObject::setProperty()
, without knowing anything about the owning class except the property's name. In the code snippet below, the call to QAbstractButton::setDown()
and the call to QObject::setProperty()
both set property "down".
QPushButton *button = new QPushButton;
QObject *object = button;
button->setDown(true);
object->setProperty("down", true);
Accessing a property through its WRITE
accessor is the better of the two, because it is faster and gives better diagnostics at compile time, but setting the property this way requires that you know about the class at compile time. Accessing properties by name lets you access classes you don't know about at compile time. You can discover a class's properties at run time by querying its QObject
, QMetaObject
, and QMetaProperties
.
QObject *object = ...
const QMetaObject *metaobject = object->metaObject();
int count = metaobject->propertyCount();
for (int i = 0; i < count; ++i) {
QMetaProperty metaproperty = metaobject->property(i);
const char *name = metaproperty.name();
QVariant value = object->property(name);
...
}
In the above snippet, QMetaObject::property()
is used to get metadata about each property defined in some unknown class. The property name is fetched from the metadata and passed to QObject::property()
to get the value of the property in the current object.
Example
Suppose we have a class MyClass, which is derived from QObject
and which uses the Q_OBJECT
macro in its private section. We want to declare a property in MyClass to keep track of a priority value. The name of the property will be priority, and its type will be an enumeration type named Priority
, which is defined in MyClass.
We declare the property with the Q_PROPERTY()
macro in the private section of the class. The required READ function is named priority, and we include a WRITE function named setPriority
.
The enumeration type must be registered with the Meta-Object System using the Q_ENUM()
macro. The macro registers an enum type with the meta-object system. This will enable useful features; for example, if used in a QVariant
, you can convert them to strings. Likewise, passing them to QDebug
will print out their names. It must be placed after the enum declaration in a class that has the Q_OBJECT
or the Q_GADGET
macro.
Registering an enumeration type makes the enumerator names available for use in calls to QObject::setProperty()
. We must also provide our own declarations for the READ and WRITE functions.
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged)
public:
MyClass(QObject *parent = 0);
~MyClass();
enum Priority { High, Low, VeryHigh, VeryLow };
Q_ENUM(Priority)
void setPriority(Priority priority);
Priority priority() const;
signals:
void priorityChanged(Priority);
private:
Priority m_priority;
};
Given a pointer to an instance of MyClass or a pointer to a QObject
that is an instance of MyClass, we have two ways to set its priority property:
MyClass *myinstance = new MyClass;
QObject *object = myinstance;
myinstance->setPriority(MyClass::VeryHigh);
object->setProperty("priority", "VeryHigh");
In the example, the enumeration type that is the property type is declared in MyClass and registered with the Meta-Object System using the Q_ENUM()
macro. This makes the enumeration values available as strings for use as in the call to setProperty()
. Had the enumeration type been declared in another class, its fully qualified name (i.e., OtherClass::Priority
) would be required, and that other class would also have to inherit QObject
and register the enumeration type there using the Q_ENUM()
macro.
A similar macro, Q_FLAG()
, is also available. Like Q_ENUM()
, it registers an enumeration type, but it marks the type as being a set of flags, i.e. values that can be OR'd together. An I/O class might have enumeration values Read
and Write
and then QObject::setProperty()
could accept Read | Write
. Q_FLAG()
should be used to register this enumeration type.
Dynamic Properties
QObject::setProperty()
can also be used to add new properties to an instance of a class at runtime. When it is called with a name and a value, if a property with the given name exists in the QObject
, and if the given value is compatible with the property's type, the value is stored in the property, and true is returned. If the value is not compatible with the property's type, the property is not changed, and false is returned. But if the property with the given name doesn't exist in the QObject
(i.e., if it wasn't declared with Q_PROPERTY()
), a new property with the given name and value is automatically added to the QObject
, but false is still returned. This means that a return of false can't be used to determine whether a particular property was actually set, unless you know in advance that the property already exists in the QObject
.
Note that dynamic properties are added on a per instance basis, i.e., they are added to QObject
, not QMetaObject
. A property can be removed from an instance by passing the property name and an invalid QVariant
value to QObject::setProperty()
. The default constructor for QVariant
constructs an invalid QVariant
.
Dynamic properties can be queried with QObject::property()
, just like properties declared at compile time with Q_PROPERTY()
.
Properties and Custom Types
Custom value types used by properties need to be registered using the Q_DECLARE_METATYPE()
macro so that their values can be stored in QVariant
objects. This makes them suitable for use with both static properties declared using the Q_PROPERTY()
macro in class definitions and dynamic properties created at run-time.
The declaration is done by putting the Q_DECLARE_METATYPE()
macro in the header file of your custom class. For example:
#include <QMetaType>
class YourCustomClass
{
public:
YourCustomClass();
};
Q_DECLARE_METATYPE(YourCustomClass)
Adding Additional Information to a Class
Connected to the property system is an additional macro, Q_CLASSINFO()
, that can be used to attach additional name--value pairs to a class's meta-object, for example:
Q_CLASSINFO("Version", "3.0.0")
Like other meta-data, class information is accessible at run-time through the meta-object; see QMetaObject::classInfo()
for details.
In this exercise you'll get familiar with accessing QObject's properties. You'll find the instuctions in reflection.cpp
.
Hint: QVariant
will be useful.
This exercise is tad bit larger. You'll create a custom class Student
, as well as implement the functionality for StudentRegistry
. You'll find the instuctions in studentregistry.cpp
.
Object Communicating: Signals and Slots
Nearly all UI toolkits have a mechanism to detect a user action, and respond to this action. Some of them use callbacks, others use listeners, but basically, all of them are inspired by the observer pattern.
Observer pattern is used when an observable object wants to notify other observers objects about a state change. Here are some concrete examples:
- A user has clicked on a button, and a menu should be displayed.
- A web page just finished loading, and a process should extract some information from this loaded page.
- An user is scrolling through a list of items (in an app store for example), and reached the end, so other items should be loaded.
Observer pattern is used everywhere in GUI applications, and often leads to some boilerplate code. Qt was created with the idea of removing this boilerplate code and providing a nice and clean syntax, and the signal and slots mechanism is the answer.
Signals and slots are the key of Qt and object communication within. They are in a sense comparable to callbacks, but the difference is that they are type-safe where as callbacks typically are not. One of the benefits we could mention before starting, is that signals and slots allow you to build many-to-many connections, where as typically virtual methods are one-to-one or one-to-many, if several virtual functions are used.
We discussed the Q_OBJECT
macro in the last chapter, and it will find some relevance throughout this topic as well.
Briefly about Events
Before jumping into signals and slots, let's talk briefly about events. Events are executed in event loops. This is hardly specific to Qt, arguably most of the applications you use spend majority of their time in event loops which wait for input, let it be from user, network, or somewhere else. There can be multiple event loops, every thread will have one for example. Qt supports the use of Event Handlers but in general you'll want to use the signal and slot system. Reason we are introducing events here is that you should understand the concept of event loops as it relates to signals and slots. In the course we'll be dealing with single-threaded applications, but when you send signals across threads you should remember that the slot might not be executed immediately, and instead it might be placed in the receiving thread's event loop to wait until the control is given to that thread.
Performance
Compared to callbacks, signals and slots are slightly slower because of the increased flexibility they provide, but the difference for real applications is insignificant. In general, emitting a signal that is connected to some slots, is approximately ten times slower than calling the receivers directly, with non-virtual function calls. This is the overhead required to locate the connection object, to safely iterate over all connections (i.e. checking that subsequent receivers have not been destroyed during the emission), and to marshall any parameters in a generic fashion.
While ten non-virtual function calls may sound like a lot, it's much less overhead than any new or delete operation, for example. As soon as you perform a string, vector or list operation that behind the scene requires new or delete, the signals and slots overhead is only responsible for a very small proportion of the complete function call costs. The same is true whenever you do a system call in a slot; or indirectly call more than ten functions. The simplicity and flexibility of the signals and slots mechanism is well worth the overhead, which your users won't even notice.
Signals
Signals are emitted by an object when its internal state has changed in some way that might be interesting to the object's client or owner. Signals are public access functions and can be emitted from anywhere, but we recommend to only emit them from the class that defines the signal and its subclasses.
To define a signal, put it in the signals:
block in the class definition:
...
signals:
void valueChanged(int newValue);
...
To emit a signal, you use the emit
keyword. The keyword is purely syntactic, but it helps to differentiate it from normal function calls.
void Counter::setValue(int value)
{
if (value != m_value) {
m_value = value;
emit valueChanged(value);
}
}
When a signal is emitted, the slots connected to it are usually executed immediately, just like a normal function call. When this happens, the signals and slots mechanism is totally independent of any GUI event loop. Execution of the code following the emit
statement will occur once all slots have returned. The situation is slightly different when using queued connections; in such a case, the code following the emit keyword will continue immediately, and the slots will be executed later.
Here are some examples of signals from the QPushButton
class:
- clicked
- pressed
- released
As you can see, their names are quite explicit. These signals are sent when the user clicked (pressed then released), pressed or released the button.
These signals are automatically generated by the moc (meta-object compiler) and must not be implemented in the .cpp file. They can never have return types (i.e. use void).
Developer experience shows that signals and slots are more reusable if they do not use special types. If QScrollBar::valueChanged()
were to use a special type such as the hypothetical QScrollBar::Range
, it could only be connected to slots designed specifically for QScrollBar
. Connecting different input widgets together would be impossible.
Slots
A slot is called when a signal connected to it is emitted. Slots are normal C++ functions and can be called normally; their only special feature is that signals can be connected to them.
Here are some slots, from different classes:
QApplication::quit
QWidget::setEnabled
QPushButton::setText
If several slots are connected to one signal, the slots will be executed one after the other, in the order they have been connected, when the signal is emitted.
Since slots are normal member functions, they follow the normal C++ rules when called directly. However, as slots, they can be invoked by any component, regardless of its access level, via a signal-slot connection. This means that a signal emitted from an instance of an arbitrary class can cause a private slot to be invoked in an instance of an unrelated class.
Signal/Slot definition
As mentioned in the previous chapter, all classes that use the signal/slot system need to have the Q_OBJECT
macro in the private section of the classes definition. Here's an example of a header file for a class that implements both signals and slots.
#include <QObject>
class Counter : public QObject
{
Q_OBJECT
public:
Counter() { m_value = 0; }
int value() const { return m_value; }
public slots:
void setValue(int value);
signals:
void valueChanged(int newValue);
private:
int m_value;
};
Connecting Signals and Slots
To connect the signal to the slot, we use QObject::connect()
. There are several ways to connect signal and slots. The first is to use function pointers:
connect(sender, &QObject::destroyed, this, &MyObject::objectDestroyed);
There are several advantages to using QObject::connect()
with function pointers. First, it allows the compiler to check that the signal's arguments are compatible with the slot's arguments. Arguments can also be implicitly converted by the compiler, if needed.
You can also connect to functors or C++11 lambdas:
connect(sender, &QObject::destroyed, [=](){ this->m_objects.remove(sender); });
The traditional way to connect a signal to a slot is to use QObject::connect()
and the SIGNAL()
and SLOT()
macros. It's presented here because it's still widely used, but in general, you should use one of the newer connection types presented before. The rule about whether to include arguments or not in the SIGNAL()
and SLOT()
macros, if the arguments have default values, is that the signature passed to the SIGNAL()
macro must not have fewer arguments than the signature passed to the SLOT()
macro.
All of these would work:
connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed(Qbject*)));
connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed()));
connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed()));
But this one won't work:
connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed(QObject*)));
...because the slot will be expecting a QObject
that the signal will not send. This connection will report a runtime error. Note that signal and slot arguments are not checked by the compiler when using this QObject::connect()
overload.
In this exercise you'll create two classes to practise defining signals and slots. You'll find the exercise instructions in main.cpp
.
3rd Party libraries
You might be familiar with other signals slot mechanisms, like the Boost.Signals2 library It is possible to use Qt with a 3rd party signal/slot mechanism or even use both mechanisms in the same project. Add the following definition to your projects (.pro) file.
CONFIG += no_keywords
It tells Qt not to define the moc keywords signals
, slots
, and emit
, because these names will be used by a 3rd party library, e.g. Boost. Then to continue using Qt signals and slots with the no_keywords
flag, simply replace all uses of the Qt moc keywords in your sources with the corresponding Qt macros Q_SIGNALS
(or Q_SIGNAL
), Q_SLOTS
(or Q_SLOT
), and Q_EMIT
.
Object Communication with Events
While it is typically preferred to use signals and slots in object communication, there are cases, where the needed functionality is easier to handle with events. If we liked to emit several signals, for example, we can subclass the corresponding QObject and re-implement the event handler. The event handler can emit the signals without connecting a signal to a slot, which then emits several signals.
Qt is an event-based system. The GUI thread enters the event loop, when QCoreApplication::exec()
is called. QCoreApplication
can handle each event in the GUI thread and forward events to QObject
s. The receiving QObject
may handle or just ignore the corresponding event.
Events may be spontaneous or synthetic. Spontaneous events are created outside the application process, e.g. by the window manager, and sent to the application. In case of GUI events, a Platform Abstraction Plugin (QPA) receives the events and converts those to Qt event types. Qt events are value types, derived from QEvent
, which provides a type enumeration for each event. If we like to modify the timer event handling, it could be done by re-implementing QObject::event()
function as shown below.
First, the event type will be checked. If the event type is a QEvent::Timer
, we downcast QEvent
to QTimerEvent
to access the event-specific data. In case of a timer, it is a timerId
. The function returns a boolean value to tell the event system, if the event is propagated further to a next receiving object, if one exists. All events, which are not handled in this function, are handled by the base class.
bool QObjectSubclass::event(QEvent *event)
{
// Check the event type
if (event->type() == QEvent::Timer) {
QTimerEvent *timerEvent = static_cast<QTimerEvent *>(event);
if (timerEvent->timerId()) {
// Do something
}
}
// Base call for all unhandled events
return QObject::event(event);
}
Normally, there is no need to re-implement the event()
function, but some event-specific handler function (See: Event Handlers). For example, timer events can be handled in void CustomObject::timerEvent(QTimerEvent *event)
. Note that these functions do not return a boolean value, but they either accept or ignore the event with QEvent::accept()
and QEvent::ignore()
to tell the event system, if the event should be propagated further or not.
void QObjectSubclass::timerEvent(QTimerEvent *event)
{
// No need to check the event type or downcast, as the argument is QTimerEvent type
if (event->timerId() == m_timerId) {
// Do something
}
return QObject::timerEvent(event);
}
Event Filters
If there is a need to handle the same event in several different classes in the same way, it is easier to use event filters rather than sub-classing many types. Event filter is nothing more but a QObject
member function, which may be called before the actual event handling functions QObject::eventFilter(QObject *watched, QEvent *event)
. Similarly to the QObject::event()
function, the boolean return value tells, if the event is filtered out (true) or whether it should be propagated further (false). Only installed event filters will be actually called void QObject::installEventFilter(QObject *filterObject)
.
There are two kinds of event filters: application-wide and object local event filters. The only difference is to which object the event filter is installed. If it is installed to QCoreApplication
object, all events in the main thread will go through the event filter. If it is installed to some other QObject
subclass, only events sent to that object will go through the event filter.
Application-wide event filters are useful for debugging to check, if for example the window manager gives the expected events to the Qt application. In general, avoid application wide event filters, as calling an extra functions for each event in the application may affect the event handling performance.
In most cases, it is sufficient to handle the event in an event handler, but as we have seen, it is possible to capture the event earlier in the propagation. For example, for touch events, there is no touch-specific event handlers, so they must be handled in QObject::event()
function. Event filters are useful, if you want to have similar kind of event handling for several different types.
Custom Events
Sometimes there is no Qt event type, which is suitable for notifying about a specific action. In those cases, it is possible to create custom events. Those can be easily drived from QEvent
. Each (custom) event contains event-specific data, so you need to add a member data and implement accessor functions to get and set the data. Finally, the event must be recognized by Qt event system, so you need a unique event type for you event. You may just extend the existing event enumeration, as shown in the example below.
const QEvent::Type customEventType = QEvent::Type(QEvent::User + 1);
class CustomEvent : public QEvent
{
public:
CustomEvent();
int value() const;
void setValue(int value);
private:
int m_m_value;
};
Synchronous and asynchronous events
In Qt, events can be sent either synchronously and asynchronously. Asynchronous events are queued in the event queue QCoreApplication::postEvent()
, which is managed by one of the platform-specific sub-classes of QAbstractEventDispatcher
. Synchronous events are never queued QCoreApplication::sendEvent()
. Note also that asynchronous events are managed by the event system, which means that they must be allocated in the heap and never deleted by the developer code.
Asynchronous events are thread-safe. In fact, cross-thread signals and slots are based on asynchronous event. When a signal is emitted from an object in one thread to an object in another thread, there will be actually an event sent between the threads, assuming that the connection type is either automatic or queued. When the event is handled in another thread, the handler code calls the slot automatically.
As any thread in Qt can have its own event loop, it is important not to interrupt the event handling by calling slots or any functions directly from another thread. It is safe to use queued connections or asynchronous events. The event is queued as long as the receiving thread returns to the event loop and starts handling the new event. Also, when you for example notify from a worker thread to the GUI thread that new data is available, you should always do this asynchronously.
Implement CustomObject
by subclassing QObject, and:
- Start a timer in the constructor (e.g. 3 sec).
- Re-implement the
event()
function. Check if there's aCustomEvent
and print to the debug console: "Custom event handled: Event data """ - Re-implement the
timerEvent()
function. When your timer expires, quit the application.
Implement a CustomEvent class with a string member.
With the code provided in main.cpp
the program should print two messages to the debug console and then exit the application after the timer runs out.
Further Reading
For more detailed information about Signals and Slots, see the official Signal/Slot documentation.
Multitasking
In an event-based system, it is important to keep the GUI thread as responsive as possible and do all time-consuming tasks in worker threads. Time-consuming tasks may take just a few dozens of milliseconds or they may execute an infinite loop. In any case, blocking functions should not delay event handling in the GUI thread.
QThread
QThread
is a class to manage threads in a platform-independent way. The threads themselves are platform-specific kernel objects. QThread
provides an API to set the priority with a platform-independent enumeration - setPriority(QThread::Priority)
, start the thread - start(QThread::Priority)
, and exit from the thread event loop - exit(int returnCode)
. The start()
function results that QThread::run()
will be executed in a new thread after the new thread is scheduled. The run()
function starts a thread-specific event loop by calling QThread::exec()
. The signals started()
amd finished()
notify, when the thread is going to be started, i.e. the run()
function is not yet started and after the thread has finished executing the event loop, respectively.
Note that there is also a terminate()
function to kill the thread. The use of this function is not encouraged, as it terminates the thread immediately possibly in the middle of data handling or holding a locked mutex object. This may break the data integrity or result to deadlocks. The best practise is always nicely return from the event loop and then thread run()
function.
If there is no need to control thread execution, it is much easier to use QRunnable
objects. Using runnables requires a re-implementation of the run()
function, but runnables will be executed in threads, provided by the thread pool and there is no need to create and cleanup threads manually.
Thread Affinity
Thread affinity defines to which thread a QObject
instance belongs to. This information is needed to decide, whether a signal between two objects should be queued or not, if automatic connection type is used. In practise, the thread affinity is just QThread *QObject::thread()
return value. If the value is 0, the thread cannot receive signals or posted events.
Developers should pay extra attention to have a correct thread affinity in their Qt programs. Although the concept is rather straightforward, it’s easy to create nasty errors, which seem to happen randomly and are challenging to debug and test. For example, in the class declaration below, it is easily possible to have wrong thread affinity for the timer member. MyThread
is instantiated in the calling thread, which means that its QObject
members will be instantiated in the calling thread as well. If we try to start or stop the timer in the run()
function, there will be a run-time error: “Timers cannot be started from another thread”.
class MyThread : public QThread
{
public:
explicit MyThread();
protected:
void run() override;
private:
QTimer m_timer;
};
To fix the problem, you need to change the timer thread affinity before it will be used in the run()
function. QObject::moveToThread(QThread *)
changes the object’s thread affinity. The effect is the same, as the timer had been created in the run()
function. Pay attention to the base call in the run()
function to start the thread event loop.
MyThread::MyThread()
{
m_timer.moveToThread(this);
connect(&m_timer, &QTimer::timeout, [] () {
qDebug() << "Timer expired";
});
}
void MyThread::run()
{
m_timer.start(1000);
QThread::run();
}
Usually, there is no need to subclass QThread
at all. The recommendation is that you create a worker QObject
and then change the thread affinity of that object. Typically, this results to less error-prone code.
Background Tasks
Let’s see, how to create background tasks with worker objects without subclassing QThread
. A trivial worker object should have a function, which is executed in the thread. In the following example, it is the run()
function. The worker is finished after the timer expires, which results that the thread gets a notification that it can quit the event loop. Note that the timer is a pointer member and in the worker constructor, the timer parent is set to the worker itself. This is convenient, as when we change the worker thread affinity, all its children thread affinity will change as well. The parent and its children cannot have different thread affinity.
class WorkerObject : public QObject
{
Q_OBJECT
public:
explicit WorkerObject(QObject *parent = nullptr);
virtual void run();
Q_SIGNALS:
void finished();
private:
QTimer *m_timer;
};
WorkerObject::WorkerObject(QObject *parent)
: QObject(parent)
, m_timer(new QTimer(this))
{
}
The code needed to start the worker becomes quite trivial boilerplate code. We create the worker and thread objects, and change the worker thread affinity to the new thread. We must not start the worker before the thread is running. Otherwise the worker is executed by the main thread. After the worker is finished it notifies the main thread and the new thread to quit from the event loops. After the thread has finished the event loop, we cleanup the heap.
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
auto *thread = new QThread;
auto *worker = new WorkerObject;
worker->moveToThread(thread);
// Start the worker after the thread has been started
QObject::connect(thread, &QThread::started, worker, &WorkerObject::run);
// Worker finsihed. Stop the thread and main thread event loops
QObject::connect(worker, &WorkerObject::finished, thread, &QThread::quit);
QObject::connect(worker, &WorkerObject::finished, &a, &QCoreApplication::quit);
// Cleanup
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
QObject::connect(thread, &QThread::finished, worker, &WorkerObject::deleteLater);
thread->start();
return a.exec();
}
Graceful Death
If the worker object is running a busy loop, how we should nicely finish the thread. Remember that we should avoid using QThread::terminate()
. QThread
provides nice functions to request thread - interruption requestInterruption()
and to check, whether the interruption has been requested - isInterruptionrequested()
. These functions can be even used in threads, which are not running an event loop. When the timer expires, our worker stops the timer and notifies the thread to be stopped. Any event or signal can be used to interrupt the thread.
void WorkerObject::run()
{
m_timer->start(1000);
connect(m_timer, &QTimer::timeout, [this] () {
m_timer->stop();
thread()->requestInterruption();
});
while (!thread()->isInterruptionRequested()) {
qDebug() << "Still running";
QThread::currentThread()->eventDispatcher()->processEvents(QEventLoop::AllEvents);
}
qDebug() << "Thread finished";
Q_EMIT finished();
}
Our worker object is running a busy loop, as long as the timer expires. While in the busy loop, the thread does not handle any events, so we need to check occasionally, if there are any events to be handled. The event dispatcher function processEvents()
allows us to handle any pending events. Without this function, our busy loop would run forever.
We have used signals and slots heavily to communicate between the threads. They are thread safe and make the inter-thread communication between threads rather straightforward. In addition to signals and slots, you may use QMetaObbject::invokeMethod()
. This is useful to notify, for example state changes from a worker thread to a GUI thread. Never use direct connections or direct function calls, when notifying the GUI thread from worker threads.
Thread Synchronization
Qt provides several lock types for mutual exclusion and thread synchronization. QMutex
provides a recursive mutual exclusion lock. There are also QReadLocker
and QReadWriteLocker
to optimize locking in cases, where most shared data accesses will be read only. QSemaphore
provides a counting semaphore between threads in a single process and QSystemSemaphore
between threads in multiple processes. QWaitCondition
can be used to synchronize threads, waiting for a condition to become true. Please read further information from here.
Implement a WorkerObject, which is executed in a separate thread from the GUI thread.
- The worker object should calculate Fibonacci numbers up to a requested number (e.g. if 5 numbers are requested, you should get 0,1,1,2,3)
- The worker object should have a timer to interrupt calculations.
- The worker object thread should call the slot in FibonacciApplication to show fibonacci values from 0 to the requested number.
- The worker finishes, if all Fibonacci values up to the requested number have been calculated OR the timer expires. In both cases you should nicely exit from the application.
Create a FibonacciApplication by subclassing QCoreApplication and add a slot, which prints out a number into the debug console.
Parent-Child relationship
The Object Tree
QObject
s organize themselves in object trees. When you create a QObject
with another object as parent, it's added to the parent's children()
list, and is deleted when the parent is. It turns out that this approach fits the needs of GUI objects very well. For example, a QShortcut
(keyboard shortcut) is a child of the relevant window, so when the user closes that window, the shortcut is deleted too.
QQuickItem
, the basic visual element of the Qt Quick module which we'll discuss in later parts of the course, inherits from QObject
, but has a concept of the visual parent which differs from that of the QObject
parent. An item's visual parent may not necessarily be the same as its object parent. See Concepts - Visual Parent in Qt Quick for more details.
You can also delete child objects yourself, and they will remove themselves from their parents. For example, when the user removes a toolbar it may lead to the application deleting one of its QToolBar
objects, in which case the toolbar's QMainWindow
parent would detect the change and reconfigure its screen space accordingly.
Object and pointer permanence
When QObjects
are created on the heap (i.e., created with new
), a tree can be constructed from them in any order, and later, the objects in the tree can be destroyed in any order. When any QObject
in the tree is deleted, if the object has a parent, the destructor automatically removes the object from its parent. If the object has children, the destructor automatically deletes each child. No QObject
is deleted twice, regardless of the order of destruction.
When QObjects
are created on the stack, the same behavior applies. Normally, the order of destruction still doesn't present a problem. Consider the following snippet:
int main()
{
QWidget window;
QPushButton quit("Quit", &window);
...
}
The parent (window), and the child (quit) are both QObject
s because QPushButton
inherits QWidget
, and QWidget
inherits QObject
. This code is correct: the destructor of quit is not called twice because the C++ language standard (ISO/IEC 14882:2003) specifies that destructors of local objects are called in the reverse order of their constructors. Therefore, the destructor of the child, quit, is called first, and it removes itself from its parent, window, before the destructor of window is called.
But now consider what happens if we swap the order of construction, as shown in this second snippet:
int main()
{
QPushButton quit("Quit");
QWidget window;
quit.setParent(&window);
...
}
In this case, the order of destruction causes a problem. The parent's destructor is called first because it was created last. It then calls the destructor of its child, quit, which is incorrect because quit is a local variable. When quit subsequently goes out of scope, its destructor is called again, this time correctly, but the damage has already been done.
To summarize:
- Tree can be constructed in any order
- Tree can be destroyed in any order
- if object has parent: object first removed from parent
- if object has children: deletes each child first
- No object is deleted twice
Please note that Parent-child relationship is NOT the same things as inheritance.
The Dangling Pointer Problem
The object tree does not solve the dangling pointer problem, but QPointer provides a guarded pointer for QObject
. When the referenced object is destroyed, it sets the pointer to 0. It's be easy to mix guarded and normal pointers. The guarded pointer is automatically cast to the pointer type.
Qt objects may also notify observers just before their destruction.
This exercise will be mostly for the student to play around with the concept of parents and children. It is not tested in TMC, and doesn't award points. You'll find instructions in parenting.cpp
.
Exercise for Part 2 - Directory Browser
This exercise is the first that you'll do outside of TMC. Download the template from the above link, and complete it following the exercise instructions.
After you're done, create a repository for your solution on GitHub, and submit it to be peer-reviewed below. Please note that to get the points for this exercise, you're required to review another student's solution! You can download an example solution and do the peer-review after you've submitted your own solution.
Complete the implementation of a trivial directory browser. You are provided with a simple browser QML UI and your task is to complete the implementation. The UI shows a directory name and number of file entries and a list of all entries. If the grid at the top is clicked, the application should sort the files to descending or ascending order, depending on the current order. Clicking on the file will show the files in that folder or open a text editor, if the file is a text file.
- Create a new class
DirManager
by derivingQObject
to implement the requested functionality. - Instantiate and expose your object to the root context of the QML engine in
main.cpp
line 11. - When a program is started, read at least file names and sizes of a directory, e.g. home, into a container. Note that the container must be a string list, so concatenate the name and size to a single string.
The QML UI expects the following API from your QObject
sub-class.
- There must be two properties:
dirName
of typeString
andfilesInDir
of type int. The values should correspond the real values of the current directory. - Provide the slot functions to be called from the UI.
model()
returns the container.fileContent()
returns the content of a text file as String.sort()
will sort entries in ascending or descending order.entryChanged(QString)
is called from the UI. You should check, whether the entry is a directory or a file. In the former case, you should read new entries from the new directory and in the latter case, you should read content of the file and usefileContent()
to return the content to the UI.
The UI is heavily based on signals.
dirNameChanged()
indicates the directory name has changed, i.e. the user has clicked on the directory name on the UI.- Similarly,
filesInDirChanged()
indicates that thefilesInDir
value has changed. fileContentChanged()
is similar, indicating the user has clicked on the file name in the UI and new data has been read from the file.dataChanged()
notifies the container content has changed.entryClicked(QString)
signal is emitted from the UI. You should handle this by checking the type of the entry and by reading either a directory content to the container or file content to the String, used in the UI.