Beneath Q_OBJECT and signals/slots

The Qt building system should be clearer now. Still, the Q_OBJECT macro and the signal/slot/emit keywords are still black boxes. Let's dive into Q_OBJECT.

The truth lies in the source code; Q_OBJECT is defined in the file qobjectdefs.h (in Qt 5.7):

#define Q_OBJECT  
public:  
    // skipped details 
    static const QMetaObject staticMetaObject;  
    virtual const QMetaObject *metaObject() const;  
    virtual void *qt_metacast(const char *);  
    virtual int qt_metacall(QMetaObject::Call, int, void **);  
    QT_TR_FUNCTIONS  
private:  
    // skipped details  
qt_static_metacall(QObject *, QMetaObject::Call, int, void **); 

This macro defines some static functions and a static QMetaObject. The body of these static functions is implemented in the generated moc file. We will not drown you in the gory details of the QMetaObject class. The role of this class is to store all the metainformation for the QObject subclass. It also maintains a correspondence table between the signals and slots of your class, and to the signals and slots of any connected class. Each signal and each slot is assigned with a unique index:

  • The metaObject() function returns the &staticMetaObject for a normal Qt class and a dynamicMetaObject when working with QML objects.
  • The qt_metacast() function performs a dynamic cast using the name of the class. This function is required because Qt does not rely on standard C++ RTTI (Runtime Type Information) to retrieve meta data about an object or a class.
  • The qt_metacall()directly calls an internal signal or slot by its index. Because an index is used rather than a pointer, there is no pointer dereferencing, and the generated switch case can be heavily optimized by the compiler (the compiler can directly include the jump instruction to the specific case very early on, avoiding a lot of branch evaluation). Thus, the execution of the signal/slot mechanism is quite fast.

Qt also adds non-standard C++ keywords to manage the signal/slot mechanism, namely  signalsslots, and emit. Let's see what is behind each one and see how everything fits inside a connect() function.

The slots and signals keywords are also defined in qobjectdefs.h:

#     define slots 
#     define signals public 

That is right: slots points to nothing and the signals keyword is just a placeholder for the public keyword. All your signals/slots are just... functions. The signals keyword is forced to be public to make sure that your signal functions are visible outside of your class (what is the point of a private signal anyway?). The Qt magic is simply the ability to emit a signal keyword to any connected slot keyword without knowing the detail of the class implementing this slot. Everything is done through the QMetaObject class implementation in the moc file. When a signal keyword is emitted, the function QMetaObject::activate() is called with the changed value and the signals index.

The last definition to study is emit:

# define emit 

So many definitions of nothing, it is almost absurd! The emit keyword is completely useless from a code perspective; moc plainly ignores it and nothing particular happens with it afterwards. It is merely a hint for the developer to notice he is working with signal/slots rather than plain functions.

To trigger a slot, you must connect your signal keyword to it using the QObject::connect() function. This function creates a new Connection instance that is defined in qobject_p.h:

struct Connection 
    { 
        QObject *sender; 
        QObject *receiver; 
        union { 
            StaticMetaCallFunction callFunction; 
            QtPrivate::QSlotObjectBase *slotObj; 
        }; 
        // The next pointer for the singly-linked ConnectionList 
        Connection *nextConnectionList; 
        //senders linked list 
        Connection *next; 
        Connection **prev; 
        ... 
    }; 

The Connection instance stores a pointer to the signal emitter class (sender), the slot receiver class (receiver), and the indexes of the connected signal and slot keywords. When a signal is emitted, every connected slot must be called. To be able to do this, every QObject has a linked list of Connection instances for each of its signal, and the same linked list of Connection for each of its slot keywords.

This pair of linked lists allows Qt to properly walk through each dependent slot/signal couple to trigger the right functions using the indexes. The same reasoning is used to handle the receiver destruction: Qt walks through the double linked list and removes the object from where it was connected.

This walk happens in the famous UI thread, where the whole message loop is processed and every connected signal/slot is triggered according to the possible events (mouse, keyboard, network, and so on). Because the QThread class inherits the QObject, any QThread can use the signal/slot mechanism. Additionally, the signals keyword can be posted to other threads where they will be processed in the receiving threads' event loop.