Changes to the Meta-Object System in Qt 5

Published Friday June 22nd, 2012
21 Comments on Changes to the Meta-Object System in Qt 5
Posted in C++, Qt

There are changes to the meta object-system in Qt 5, both under the hood and on the API side. Some of the changes are source incompatible with Qt 4. In this blog I’ll describe those changes and how to adapt existing code. I’ll also show a few small API additions that make it easier to work with QMetaMethods.

Removing support for Qt 4 meta-object versions

The meta-object data (e.g., as created by moc) has a version number that describes the contents (format/layout) and features of the meta-object. Whenever we add a new feature to the meta-object (such as making a class’s constructors introspectable) in a new Qt (minor) release, we must also increase the meta-object version. The “last generation” Qt 4 meta-objects have version 6.

To maintain backward compatibility, new minor versions of Qt have to support the meta-objects of previous versions. That’s achieved by checking the version of the individual meta-object at relevant places inside Qt, and falling back to an alternate codepath if the meta-object is of old age.

Since Qt 5 breaks binary compatibility with Qt 4 anyway, we took the opportunity to get rid of the support for old meta-object versions. The fallbacks would effectively be dead code in Qt 5, since Qt would never create a meta-object with a “Qt 4” version. Qt 5 meta-objects currently have version 7.

moc output from Qt 4 does not compile with Qt 5. In (the hopefully rare) case your codebase contains some hand-edited moc output (which Qt itself does in a few places, to implement “special” meta-objects), you’ll have to recreate the moc output.

The various runtime meta-object builders in Qt (QMetaObjectBuilder, QtDBus, ActiveQt) have also been updated to generate version 7 meta-objects; since these are internal, existing code shouldn’t be affected. (Hopefully nobody out there wrote their own meta-object builder… 🙂 )

Methods are no longer identified by strings

Prior to Qt 5, the meta-object contained the full, normalized signatures of the class’s signals, slots and Q_INVOKABLE methods, stored verbatim as zero-terminated strings. This probably made sense back when QObject::connect() was fully string-based (using the SIGNAL() and SLOT() macros). But now that there’s a template-based QObject::connect(), storing the full signature is a less obvious choice. (Dealing with signature strings internally was always necessary in Qt 4 due to the QObject::connectNotify() function; more on that in the next section.)

Another common use case of method introspection is dynamic bindings (QML, QtScript), where the normalized signature is, again, not an ideal representation to work with. The implementation would be simpler and more efficient if it had direct access to a method’s name and parameter types (no runtime parsing of strings required).

Lo and behold: In Qt 5, QMetaMethod has new functions name(), parameterCount(), and parameterType(int index). The full method signature is no longer stored verbatim in the meta-object data (only the method name is), since a compatible signature can be constructed on demand from the other bits of information.

Unfortunately, the existing QMetaMethod::signature() function returns a const char pointer; this leaked the fact that the signature used to be stored verbatim. We couldn’t simply change the return type to QByteArray, since implicit casting would cause existing code like the following to break without warning:

// If signature() returns a QByteArray, it would go out of scope (and be destroyed) after this statement:
const char *sig = someMethod.signature();
// Do something with sig and watch bad stuff happen, probably ...

To resolve this, we’ve introduced a new function in Qt 5, QMetaMethod::methodSignature(). The old QMetaMethod::signature() function is no longer available; calls to it will trigger a compiler error stating that signature() has been renamed. Existing code should be ported to use QMetaMethod::methodSignature(). Even with new functions like QMetaMethod::name(), obtaining the full method signature can still be useful in some cases, e.g. for debugging.

connectNotify() and disconnectNotify()

QObject::connectNotify() and disconnectNotify() are two rarely-reimplemented virtual functions that you can use to do something when someone connects to or disconnects from one of your class’s signals. One potential use is to implement lazy proxy (relaying) connections from some internal (“back-end”) object to the public signals. The qtsystems module does that heavily, for example.

Prior to Qt 5, connectNotify() was biased towards the string-based QObject::connect(), in that it received a const char pointer to a normalized signature that identified the signal that was connected to. For the template-based QObject::connect(), and for custom connections done in e.g. QML or QtScript, making Qt prepare a string representation of the signal just to call a virtual function that’s usually not reimplemented is sub-optimal.

Furthermore, the char pointer-based connectNotify() feels … un-Qt-ish. Even if you managed to spell the normalized form of the signature, you could still fall into the following trap (it happened inside Qt, even):

void MyClass::connectNotify(const char *signal)
{
    if (signal == SIGNAL(mySignal())) {
        // This will never get reached; comparing pointers, not strings ...

The documentation says you should wrap the signal argument in a QLatin1String, but it’s easy to forget to do so. The solution is to provide an API that doesn’t allow for such mistakes.

In Qt 5, QObject::connectNotify() and disconnectNotify() receive a QMetaMethod instead of a char pointer. A QMetaMethod is just a thin wrapper around a QMetaObject pointer and an index. It’s not biased towards any particular way a connection is established.

This change also allowed us to move the calls to connectNotify() and disconnectNotify() to the implementation of the internal index-based connect() and disconnect() functions (QMetaObject::connect(), which is used several places in Qt). In practice, this means that your connectNotify() reimplementation will get called as you would expect, even if QObject::connect() isn’t called explicitly (connectSlotsByName(), for example). Finally, we could get rid of some blush-inducing code in Qt that manually called connectNotify(const char *) in conjunction with the index-based connect().

Existing code that reimplements connectNotify() and disconnectNotify() needs to be ported to the new API. There are two new functions that should make it relatively easy: QMetaMethod::fromSignal() and QObject::isSignalConnected().

QMetaMethod::fromSignal()

The new static function QMetaMethod::fromSignal() takes a member function (a signal) as argument and returns the corresponding QMetaMethod. It nicely complements the new connectNotify():

void MyClass::connectNotify(const QMetaMethod &signal)
{
    if (signal == QMetaMethod::fromSignal(&MyClass::mySignal)) {
        // Someone connected to mySignal ...

To avoid doing a specific signal lookup every time connectNotify() is called, you could keep the result of QMetaMethod::fromSignal() in a static variable.

Another obscure usage of fromSignal() is to perform a queued emission of a signal (QMetaObject::invokeMethod() would also do the trick, but it’s string-based):

QMetaMethod::fromSignal(&MyClass::mySignal)
    .invoke(myObject, Qt::QueuedConnection /* and arguments, if any ... */);

QObject::isSignalConnected()

The new function QObject::isSignalConnected() can be used to check whether anything is connected to a signal. It’s a QMetaMethod-based alternative to QObject::receivers().

void MyClass::disconnectNotify(const QMetaMethod &signal)
{
    if (signal == QMetaMethod::fromSignal(&MyClass::mySignal)) {
        // Someone disconnected from mySignal
        if (!isSignalConnected(signal)) {
            // No more connections to this signal; we can release some resources now, perhaps ...

Alternatively, you can use this function to avoid emitting a signal altogether, e.g. if the emit would require some complex computations that could notably affect application performance.

QTBUG-4844: QObject::disconnectNotify() is not called when receiver is destroyed

UPDATE: Fixed by https://codereview.qt-project.org/#change,29423

This bug is currently still present, unfortunately. It’s a deficiency that effectively renders disconnectNotify() “incomplete” (or “broken”, depending on how you see it), so I’d really like to see the issue resolved.

To be able to implement the desired behavior efficiently, the connections would have to remember the signal id, since, when the receiver is destroyed, we cannot derive the signal id as we can in a normal (explicit) disconnect. This would add size overhead (one int) for all connections. If you can come up with a clever solution, there’s still time to get it into 5.0 (it’s likely too big of a behavior change to fix in a future minor release).

Method return type

Prior to Qt 5, you had to use QMetaMethod::typeName() to determine a method’s return type, in const char pointer form. Quirkily, typeName() returned an empty string if the return type was void — not the string “void”, as QMetaType::typeName() does. This inconsistency led to confusion; I’ve seen code that does

if (!method.typeName() || !*method.typeName() || !strcmp(method.typeName(), "void")) {
    // The return type is void ...

… just to be sure.

In Qt 5, you can use the new function QMetaMethod::returnType() instead, which returns a meta-type id:

if (method.returnType() == QMetaType::Void) {
    // The return type is void ...

In Qt 4, you couldn’t use QMetaType::Void to distinguish between void and unregistered types (they were both represented as the integer 0). In Qt 5, QMetaType::Void actually means the type void; the new value QMetaType::UnknownType is used to specify a type that isn’t registered with Qt’s type system.

(As an aside: If you have existing code that compares a type id to QMetaType::Void (or to the integer 0), I recommend double-checking your logic when switching to Qt 5 (“should this be checking for void, unknown types, or both?”).)

For consistency with QMetaType, QMetaMethod::typeName() in Qt 5 returns the string “void” if the return type is void. Existing code that assumes that an empty typeName() string means void will have to be adapted accordingly (compare the returnType() to QMetaType::Void instead).

Method parameters

Similar to QMetaMethod::returnType(), the new function QMetaMethod::parameterType() returns the meta-type id of a parameter. QMetaMethod::parameterCount() returns the number of parameters. Consider using those two functions instead of the old QMetaMethod::parameterTypes() function, which returns the types (names) as a list of strings.

If the type is known at meta-object definition time (a built-in type, in the moc case), the type id will be embedded directly in the meta-object data, resulting in very fast lookup. For other types, the id resolution will fall back to string-based lookup, which is no slower than before. (It could be improved by caching the resolution somehow.)

Conclusion

For Qt 5, the meta-object system (and the meta-type system, too, but let’s save the details for another post) has gone through a small metamorphosis. Methods are now represented and manipulated with proper semantics, not as plain (C) strings. Source compatibility with Qt 4 was largely kept (if it ain’t broke, don’t fix it — but a few things were broke in Qt 4, and we were compelled to fix them). We cleaned out some legacy stuff in the implementation. Several Qt modules already use the new features, resulting in cleaner, faster code. Surely even the most staunch meta-critic will be appeased.

Do you like this? Share it
Share on LinkedInGoogle+Share on FacebookTweet about this on Twitter

Posted in C++, Qt

21 comments

smoggy says:

Here are some questions/suggestions:
In order of importance, could it be possible to :

1) introspect what QObject::(signals+slots) are connected to a certain signal ?

2) serialize the object connections at runtime, say to Json ? to load them in another session later. Either from C++ or from QML. We don’t need the implementation, just the possibility to.

3) ? is it possible to connect QString to QByteArray or const char * ? is const QString == QString == QString & == QByteArray when connecting arguments ?

Dario says:

Slow clap. Thank you guys, this news made me smile, keep up the awesome job.

Fonzi says:

I love the changes; it makes the meta-object API much cleaner/simpler to use. Thanks for the hard work! 🙂

Scorp1us says:

For the disconnectNotify() bug, why can’t you have the destructor signal the connectoed object, and for any signal in that object, call disconnectNotify? When you delete something, it does not matter what specific signal has to be disconnected, as they will all be gone shortly. This way you won’t have to store the int.

Maybe call the slot deleteNotify()?

d3fault says:

Thank you for this, looks great. The move from run-time interpretting of strings to compile time checking is a much welcomed change.

Phil Thompson says:

You say…

Hopefully nobody out there wrote their own meta-object builder

…but this is what every language bindings author has to do. At last year’s Qt Contributors Summit this was explained and a request for an API to do this was made – but not implemented it seems.

@smoggy: 1) Can you tell us what your use-case for this is? Is it just for debugging, in which case we don’t need an API, or do you have something more specific in mind? In general, signals and slots are an interface point: they shouldn’t care what is on the other side. If it needs to know, there’s something wrong with the design. That’s why QObject::isSignalConnected returns a simple boolean.

2) serialising the connections doesn’t make sense because the pointer addresses aren’t serialisable. How will the deserialiser know what object it is referring to? What’s more, remember that you can connect a signal to a static function or a lambda. There’s no way to serialise those.

3) Type conversion is possible with the new C++11 connect. At compile time of your own code a type A can be created from a type B, then a signal somethingChanged(B) will connect to a slot doSomething(A). That’s valid for QStrings and QByteArrays if you did not #define QT_NO_CAST_FROM_ASCII. The way this is implemented is that the connection actually happens to a small stub helper from the application code that takes a B and creates an A from it before calling the final destination function. It’s all implicit.

It will not work with the older string-based SIGNAL() and SLOT()-based connect.

Peter Kümmel says:

Good to cleanup here, but className still returns a const char*

const char *className() const;

Is there a reason for this?

Peter Kümmel says:

Also all the QMetaObject::indexOf functions have a C-API by using const char*.

MJ says:

Great job! But I’m worry about ‘normalized signatures’. Is that negative to performance? And ‘Qt uses normalized signatures to decide whether two given signals and slots are compatible. ’ So, is it possible that Qt Creator could use this feature to tip all the slots that can be connected ??

Benjamin Zeller says:

I use the metaobject informations for a rpc based solution.
I serialize all informations about metamethods,signals,slots,properties
on the serverside and rebuild a metaobject on the clientside.
This makes it possible that you can even connect() to signals and slots
on the clientside just as you were using it locally.

Will this still be possible with the new metaobject backend?

@Thiago: Stop calling it the “C++11 connect”, The new connection syntax works perfectly with plain C++98. (Only usage of lambda expressions requires C++11). You calling it like that lead people to think they can’t use that syntax (the template connect) if they want to keep working with C++98.

So to rephrase: Type conversion is possible with the new template-based connect.

smoggy says:

@Thiago Macieira
1+2) The case is an idea i had for a runtime connection serializer for robotics. It would use dataflow components to (dis)connect the different components in the framework. Something like simulink.

E.g. a camera image provider component that emits a QImage as a signals to listeners, say a edge detection component that has a QSlot: input_image(QImage).
At runtime it is possible to link these components.

Also when wrapping classes that have signal+slots to expose to external services it is important to have a way to introspect them at runtime to create connections from the service to the c++ classes. Say a runtime xml-rpc wrapper for example, or a dbus interface that is generated at runtime from the QObject description.

João Barbosa says:

@smoggy That kind of dataflow algorithms doesn’t model well with signals & slots, specially if the components do the work in proper threads. You don’t want the signal to be queued in the event loop. For instance, the camera image provider probably will emit the image from it’s capturing thread. Or some heavy image processing algorithm needs a thread. You don’t want to add overhead to the event loop. I recommend implementing the model that fits you the best. Gstreamer does this well with async queues.

Kent Hansen says:

@Phil Thompson: Was a proposal for a meta-object builder API posted anywhere? You could give QMetaObjectBuilder a try; it’s internal, but the API hasn’t changed much the last couple of years. I’d still like to port QtDBus and ActiveQt to use QMetaObjectBuilder, to make sure QMOB supports everything (and so we would only have _one_ generator in Qt…). In the case of QtDBus, for example, the meta-objects contain some extra data which isn’t possible to create through QMOB currently.

Kent Hansen says:

@Peter Kümmel: Regarding the rest of the const char * API: Yes, there’s still a bunch of it. We ran out of time to fix it for Qt 5. (Overloads for functions like indexOfXXX can still be added in a minor release, though.)

Tony Otto says:

Will QMetaType replace QVariant::Type in Qt5?

DG says:

A few months ago while dabbling with qmlRegisterType(), I noted that it would not support dynamic types such as those that could be made with QMetaObjectBuilder. IIRC, the qmlRegisterType() code referred to some static data member, and that made dynamic types impossible. Is this sort of thing being cleaned up for Qt5?

Pyside worked around the problem by using recursive templates to create a compile-time array of placeholder types whose “backend” would be come to life at runtime. They also had to rip open qmlRegisterType() and call the bits individually. I would obviously love to see a qmlRegisterType() that just worked with dynamic types. 🙂

Phil Thompson says:

@Kent Hansen: There was no proposed API published. After a quick look at the QMetaObjectBuilder API it seems (mostly) OK. I would add some way to get the version of meta-object that will be created by the builder. Also add a method to QMetaObject itself to return its version. Finally I would like a virtual QMetaObject factory method that would be called by toMetaObject() to create an uninitialised QMetaObject. The default implementation would just do “new QMetaObject()”, but I could implement a builder sub-class that would reimplement it to return a QMetaObject sub-class that had extra data. This would also solve your QtDBus issue. QMetaObject may need some sort of user defined sub-version so that it can be tested before casting to the sub-class – or maybe it maintains a pointer to the builder that built it?

Kent Hansen says:

@Scorp1us: The disconnectNotify bug has been fixed now (https://codereview.qt-project.org/#change,29423).

@DG: I haven’t touched qmlRegisterType(); you’d have to check with latest Qt5/master to see whether the issue with dynamic types is still there. If it is, filing a bug report where you describe your scenario (preferably with a small compileable testcase) would be good.

Commenting closed.

Get started today with Qt Download now