Prefer to use normalised signal/slot signatures

Some time ago I’ve talked with my Berlin office mates about the benefits of using normalised signal signatures in connect statements. That is, instead of

SIGNAL( rowsInserted( const QModelIndex &, int, const int ) )

write

SIGNAL(rowsInserted(QModelIndex,int,int))

That is, remove all dispensable whitespace, const-&, and top-level const. If you’re in doubt about how the normalised form of your signal or slot looks like, take a look at the string data in the moc-generated file. The signals and slots are listed with their normalised signatures there.

What was clear already then is that you’ll save a few bytes of string data if you use the shorter form. I was also expecting a zero-copy fast-path in QMetaObject::normalizedSignature() for the case of an already-normalised argument. I didn’t find it back then, and I put the issue on my to-do list to implement in Qt.

But I’ve just stumbled over the code of QObject::connect(). If you look there, you’ll see that not using the normalised form has a rather severe performance penalty:

Lookup is first attempted with the signature as-is, and only if that fails is QMetaObject::normalizedSignature() called.

That means, when using non-normalised signal/slot signatures, you not only pay for a strcpy(), but also for a doomed-to-fail first lookup attempt. Sure, connects are usually done during startup, and a profiler won’t show you, but using non-normalised signatures is hereby firmly put into the realm of premature pessimisation.

On the positive side, that means we already have the zero-copy fast-path for the case of normalised signatures, and I can strike one more thing from my to-do list 🙂

Luckily, we don’t have to wade through our source code by hand, since the Trolls provide a tool in $QTDIR/util/normalize that you can run on a project to fix the signal/slot signatures up. It doesn’t seem to be shipped in the releases, but you can download it directly from Gitorious: https://qt.gitorious.org/qt/qt/trees/v4.7.3/util/normalize

Note, however, that at the time of writing, it creates sporadic false positives when it hits Q_PRIVATE_SLOT() macros.

The util/ directory doesn’t seem to be shipped in releases, and the tool fails on (some, I haven’t found out why not on all) Q_PRIVATE_SLOT macros, but it’s of course a huge time-saver, and trivial to build even when downloaded from Gitorious.

The effect of using the normalized signature is maybe big when QStateMachine is used: on each state entry all connections are created and when the state is left all are dicsonnected.
But I haven’t checked the code if the normalized signatures are cached somehow.

I’m a little confused — does that mean that you can’t pass const reference parameters to a slot? If not, doesn’t it make debugging a lot more confusing when the connect signature is different from the function prototype of the slot?

but connect() does essentially a simple string comparison. You could normalise to a form that preserves the const-&, but there’s no point because you can’t overload

void setText( const QString & );
void setText( QString );

anyway (they will always be ambiguous), so you might as well use the shorter form, and because it doesn’t matter if arguments are passed by value or by constant reference, because the code that moc has to generate for either would be identical. Since the normalised form is identical if the source signatures would be either equivalent or else always ambiguous, I don’t think there’s any confusion.

Regarding passing const reference parameters to slots:
The normalised signature is but a name for the slot, used in the meta object system, e.g. to look up a slot by name, and as such doesn’t influence the way a direct call to the slot is compiled by the compiler, not even the one that appears in the moc-generated code. Therefore, the slot signature should follow standard C++ rules as to when to pass by value and when to pass by const-&.

David Johnson: Using “QString” instead of “const QString &” in the signature is not premature optimization, it doesn’t add any complexity, it even makes the connect statement more concise and readable. And if something doesn’t make a difference from a complexity or readability point of view, like, say i++ vs. ++i, I’d just go for the more performant solution.