Skip to content

What do I do if a slot is not invoked? A practical checklist to debug your signal/slot connections

All Qt developers have asked themselves at least once in their careers: “why isn’t my slot invoked?” (I’ve asked myself that question many, many times).

There are a number of reasons why a connection may fail to be properly set up, and ultimately cause our slot not to be invoked. This blog post is a practical series of checks to help you debug these sorts of issues.

0. Was the slot really not invoked?

First and foremost, are we really sure that the slot was not invoked? A simple way to rule this out is by adding a debug print in the slot, or running the application in a debugger and setting a breakpoint.

Keeping a debug print in place is also useful to know when we have finally fixed the problem, and the slot is getting invoked as expected.

1. Was the connection established in the first place?

A QObject::connect() statement may fail to connect a signal to a slot for various reasons. Here’s a non-comprehensive list:

  • The sender is nullptr
  • The receiver is nullptr
  • The signal does not exist for the given sender (*)
  • The slot does not exist for the given receiver (*)
  • The signal/slot argument lists is not compatible (*)

The elements marked with a (*) do not fully apply to the new connection syntax, which performs checks at compile-time.

When QObject::connect() fails it returns false and it emits a warning which gets typically printed on the console, if you have one, or in Creator’s output pane.

In practice this means we have a couple of options to test if this is happening:

  • we can test the return value (for instance, by adding an assertion), or
  • we can make all warnings fatal by exporting the QT_FATAL_WARNINGS environment variable and setting it to a non-zero value. (Of course, this is only doable if our application is warning free…)

For instance, we can do this:

// connect() actually returns a QMetaObject::Connection object,
// which implicitly converts to bool.
const bool connected = connect(sender, &Sender::aSignal, 
                               receiver, &Receiver::aSlot);
qDebug() << "Connection established?" << connected;

Whatever you do, do not wrap your connect() statement in an assert!

This is wrong:

// do not do this!
Q_ASSERT(connect(sender, &Sender::aSignal, receiver, &Receiver::aSlot));

This is the correct way:

const bool connected = connect(sender, &Sender::aSignal, 
                               receiver, &Receiver::aSlot);
Q_ASSERT(connected);
Q_UNUSED(connected);

2. Are the sender and the receiver objects still alive?

Qt automatically breaks a signal/slot connection if either the sender or the receiver are destroyed (or if context object is destroyed, when using the new connection syntax and connecting to free functions). This is a major feature of the signals and slots mechanism. However, in complicated software architectures, and when dealing with long-lived objects, we might end up in a situation where indeed a connection was broken because one side of it had been destroyed.

Again, there are a couple of ways to be sure that we didn’t accidentally delete the sender and/or the receiver. One is by adding a breakpoint in their destructors. Bonus points if we make it conditional on the specific objects, for instance like this:

(gdb) b 'QObject::~QObject()' if (this == 0x12345678) 

However, this strategy is a bit cumbersome in practice: we may need a debug build of Qt and of our application; we need to figure out the address of the object in question (say, by having another breakpoint on the connect statement and inspecting the locals), and these addresses change at every run; and so on.

An easier approach is to connect to the QObject::destroyed() signal, and output a debug message:

connect(sender, &Sender::aSignal, receiver, &Receiver::aSlot);
// be sure that sender and receiver didn't get destroyed:
connect(sender, &QObject::destroyed, 
        [] { qDebug() << "Sender got deleted!"; });
connect(receiver, &QObject::destroyed, 
        [] { qDebug() << "Receiver got deleted!"; });

3. Was the signal emitted at all?

It’s a fair question to ask, especially when the signal is coming from Qt itself or from a third-party library we have no control over. If the signal is not getting emitted, obviously the slot will never be called.

Once more, a breakpoint on the signal itself (remember that signals are ordinary member functions) or a lambda connected to it, can immediately show whether the signal is being emitted or not.

4. Is the connection queued?

Queued connections deserve some special care: for a queued connection to work, there are extra requirements compared to a direct connection. The requirements are:

  1. that an event loop is running in the thread the receiver has affinity with;
  2. that all the arguments carried by the signal are registered in the meta-type system.

Running an event loop

The first requirement comes from the fact that a queued connection under the hood is implemented by posting an event to the receiver. The handling of this event will invoke the slot. However, for that event to be dispatched, we need a running event loop.

Running an event loop can be achieved in a number of ways: calling QCoreApplication::exec() in the main thread, or QThread::exec() in another thread, or using QEventLoop, and so on.

Registering the signal’s arguments in the meta-type system

The second requirement is a consequence of the first: when a queued connection is activated, Qt needs to copy the arguments of the signal into the event sent to the receiver. In order to do so, it needs a bit of help from the user: all the types of the arguments must be registered in Qt’s meta-type system.

The good news is that all the C++ fundamental types and many Qt datatypes are already registered and will just work. The bad news is that any other datatype is not registered, in which case the connection will not work and we will get a warning on the console.

The solution is simple: use the Q_DECLARE_METATYPE macro and the qRegisterMetaType() template function — which by the way is exactly what the warning suggests you to do anyhow. See, it’s a good thing to look for warnings on the console!

For instance, if we need to establish a queued connection for a signal carrying a MyClass argument, we’ll need to modify the class’ definition:

// objects of this type are passed as arguments to some signal
// in a queued invocation
class MyClass {
/* ... */
}; 

// add this:
Q_DECLARE_METATYPE(MyClass);

and add the following statement somewhere, making sure that it runs before the signal is emitted for the first time (or before the connection is established, if we’re forcing the connection type to queued):

qRegisterMetaType<MyClass>(); // do not pass any argument

We may add this call to MyClass‘ constructor, or in main, or in some other initialization code. It is safe to call qRegisterMetaType() more than once.

Note that, as far as the meta-type system is concerned, MyClass, MyClass *, QVector<MyClass>, shared_ptr<MyClass> and so on are all different types, and as such, each one may need its own registration.

Debugging signals and slots connections

In the final part of this blog post, I’d like to offer a couple of broader suggestions to inspect and debug signals and slots issues.

How do I check if a connection is queued?

If we do not explicitly choose a connection type, then QObject::connect() defaults to Qt::AutoConnection. This connection type means:

  • if the thread that invokes the signal is the same thread the receiver has affinity with, use a direct connection;
  • (otherwise) if the thread that invokes the signal is not the same thread the receiver has affinity with, use a queued connection.

You can see this decision in action here on line 3686.

Note how:

  • the thread affinity of the sender object does not matter at all;
  • the decision is taken at signal emission time (and therefore its outcome may change, for instance if the receiver object changes thread affinity).

Apart from adding a breakpoint in Qt’s internals, an easy way to debug whether a signal activation is direct or queued is doing the same test and showing the outcome:

// the connect to debug
connect(sender, &Sender::aSignal, receiver, &Receiver::aSlot);
// add:
connect(sender, &Sender::aSignal, // same sender and signal
        receiver,                 // context object to break this connection
        [receiver]() {            // debug output
             qDebug() << "Direct?" << QThread::currentThread() == receiver->thread(); 
        },
        Qt::DirectConnection);    // see below

In the second connect(), we make use of the context object to automatically break the connection in case receiver gets destroyed; otherwise, we would risk running the lambda on a dangling pointer.

On the other hand, specifying the context object makes the lambda run in receiver‘s thread by default, making our debug output useless. For this reason, the connection type gets forced to Qt::DirectConnection, so that the lambda is invoked by the same thread that emits the signal.

Checking all the signals and slots connected to an object

It is possible to display all the connections established from and towards a given QObject (that is, that have that object as a sender or as a receiver). There are (at least) two ways for doing this.

QObject::dumpObjectInfo()

A straightforward way to see all the inbound and outbound signals is to add a call to QObject::dumpObjectInfo() on a given QObject. Since Qt 5.8 this function is unconditionally available for Qt users (cf. the small patch I made); before, using this function required a debug build of Qt (otherwise, it was a no-op).

This is an example of QObject::dumpObjectInfo()‘s output:

OBJECT TextEdit::unnamed
  SIGNALS OUT
        signal: destroyed(QObject*)
        signal: destroyed()
        signal: objectNameChanged(QString)
          <functor or function pointer>
        signal: iconSizeChanged(QSize)
          --> QToolBar::unnamed _q_updateIconSize(QSize)
  SIGNALS IN
          <-- QClipboard::unnamed <unknown>
          <-- QTextDocument::unnamed <unknown>
          <-- QAction::unnamed <unknown>
          <-- QComboBox::comboSize <unknown>

(I’ve removed some lines from the output to keep it compact).

Basically, for each signal declared in the object, we get a list of what is currently connected to it. Similarly, we get a list of all the connections using the object as the receiver. For each object we also get its type and its object name (in the sense of QObject::objectName; unnamed means that the name is empty).

As you can notice the big problem we have here is that the new style connection syntax does not allow to display the function’s name in a pretty way, and all we get is a placeholder. On the other hand, the old-style connections get the full signature printed. Remapping a function pointer to its symbol name is quite complex, and no-one wants to add that machinery to Qt itself (after all, if you need it, you can always start a debugger).

GammaRay

GammaRay, the Qt Swiss-army knife, is also able to visualize all the connections for a given QObject. Once you have started it, go in the Objects pane, select the object you want to inspect and check its connections in the Connections tab:

GammaRay showing all the connections for a QObject

By the way, GammaRay will also tell you if you have signals carrying arguments which are not registered in the metatype system. For this, switch to the Methods tab, which lists all the meta-methods (signals, slots, invokables):

GammaRay showing signals which carry arguments which are unregistered metatypes

Conclusion

With this blog post, I have provided Qt users with a short, handy guide to know better their signals and slots connections and what to do in case they experience trouble.

About KDAB

KDAB is a consulting company offering a wide variety of expert services in Qt, C++ and 3D/OpenGL and providing training courses in:

KDAB believes that it is critical for our business to contribute to the Qt framework and C++ thinking, to keep pushing these technologies forward to ensure they remain competitive.

FacebookTwitterLinkedInEmail

Categories: C++ / KDAB Blogs / KDAB on Qt / Qt

12 thoughts on “What do I do if a slot is not invoked?”

  1. Off topic: What is the reason that KDAB does not provide the whole content of its articles in the RSS feed any longer? It makes reading the interesting KDAB articles from within my RSS reader quite complicated. Could this be fixed? Thanks and keep up the great blog posts!

  2. I’d add a case 1.5: has the signal already been connected to the slot?

    The simple case is a constructor that emits a signal: no connection can be already in place, so the signal is just a nop. A more difficult case may be some initialization done in the constructor, and this initialization may cause an immediate emission of a signal, depending on some other conditions.

  3. Hi,
    could you explain why having Q_ASSERT around connect statements is such a bad idea? Is it the implicit conversion from QMetaObject::Connection to bool?

    1. Giuseppe D'Angelo

      Hi Christian,
      The problem comes from the fact that a Q_ASSERT gets completely removed in release mode by default. Having any code with side effects (such as a connect statement) in a Q_ASSERT is therefore dangerous — it will get removed in a release build.

    1. Giuseppe D'Angelo

      Hi,

      That’s correct. Actually, even in Qt 5 connect returns a connection object — which implictly converts to bool anyhow, so you can test it directly (if you don’t care about disconnecting using that connection object).

  4. Slot will also not be invoked if the signal is explicitly disconnected from a parcticular slot (or all the slots). It may be obvious, but it doesn’t hurt to check whether you have a line of code such as disconnect(sender, &Sender::signal, nullptr, nullptr); in your application.

    1. Giuseppe D'Angelo

      Hi,
      Sure, it makes total sense to check for that. The techniques discussed at the end of the post should allow you to spot that the signal is not connected (any more), hence one should investigate why, and clearly a disconnection can be the reason. Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *