Monday 11 May 2020

A technique for connection checking in Qt


Recently, I stumbled upon a technique for checking if the connect() call for Qt signals/slots was successfull. You may ask what the problem is - just check the result of the call, how difficult can that be?
  bool ok = connect(...);
  Q_ASSERT(ok);
This doesn't look as it's much! However, in a standard Qt project you won't see that because it doesn't scale really well. What you'll normally see is this:
  connect(a, SIGNAL(...), b, SLOT(...));
  connect(c, SIGNAL(...), d, SLOT(...));
  connect(e, SIGNAL(...), f, SLOT(...));
  // etc..
Repeated insertions of Q_ASSERT(ok) would kill readability in such case! In a couple of projects I've seen that people were trying somehow to have the cake and eat it - do not sacrifice readibility but nonetheless report a failed connect. This was done by means of custom Qt builds, where the connect() function was changed to include an assert or to throw an exception in case of failure.

Digression: I'm normally not a fan of exceptions but here they seem to be perfect a perfect match, don't they? Just signal some error condition without polluting the code with error handling! What's here not to love? But on a closer inspection it poses problem in Release builds, as in production environment we do not want to crash a program when some connection are not right. Ok, with enough testing and exception handling code it's not a problem, but asserts are a much simpler method to achieve the same goal.

Technique:

So let us proceed to the advertised technique:
    v << connect(...);
How is the shift omperator implemented? Let's have a look:
    ConnectionVerifier& ConnectionVerifier::operator<<(const QMetaObject::Connection& connection)
    {
        verify(connection);
        return *this;
    }

    void ConnectionVerifier::verify(const QMetaObject::Connection& connection)
    {
        const bool connected = (connection != nullptr);
        //Q_ASSERT(connected);
        if (!connected)
        {
            throw Exception("Could not establish signal-slot connection.");
        }
    }

Now we can easily scale:
  v << connect(a, SIGNAL(...), b, SLOT(...));
  v << connect(c, SIGNAL(...), d, SLOT(...));
  v << connect(e, SIGNAL(...), f, SLOT(...));
  // etc..
Well, this is the code as I found in to be used in the project - somehow the dychotomy of exceptions and asserts we discussed before wasn't resolved by the authors. Admittedly, both alternatives have their merits.

Summing up:

So will I use this technique? Well, it's most useful for the old connect() syntax where the signals and slots are passed as (Q)strings using the SLOT() and SIGNAL() macros - here it is very easy to accidentally misstype the name or parameters of the corresponding slot/signal, believe me,

But with advent of Qt 5 we have also the modern, type-safe syntax alternative:
  connect(a, &A::singnalA, b, &B::slotB);
which I'm using quite exlusively right now. Theoretically there are some corrner cases where it also could fail, but the standard problem we had in the old days, i.e. the typos, is solved now.

However, if you inherited some giant legacy Qt application wehich uses the old connect() syntax, you might be thankfult to have this trick up your sleeve!

2 comments:

Rudi said...

While this technique is quite elegant, I would not use it here. AFAIK, since a long time the standard builds of Qt contain the mentioned assert in connect() and will therefore spit out a message in this situation anyway. Even in release builds.

Marek Krj said...

@Rudi
No, it's not an assert, it will only log an error message, which will normally get overlooked in bigger software systems