QMetaType knows your types

QMetaType is Qt's way to have run-time dynamic information about your types. It enables things such as QVariant wrapping of custom types, copy of queued connection arguments, and more.

If you ever wondered what does Q_DECLARE_META_TYPE or qRegisterMetaType do and when to use (or not to use) them, read on. This article will describe what you need to know about QMetaType: What is its purpose; How to use it; And how it works.

Why does Qt need runtime dynamic type information?

Let's start with a bit of history. QMetaType was introduced in Qt 4.0. It was created in order to have the possibility to have asynchronous signals and slots (Qt::QueuedConnection). For queued slots to work, we have to copy the arguments and store them in an event that will be processed later. We also need to delete those copies when we are finished invoking the slot. (Note: This is not needed when using Qt::DirectConnection: pointers to arguments directly on the stack are used.)

The code dispatching signals in QMetaObject::activate has an array of pointers to arguments void*. (For more info read how signals and slots work). But, at the time, all Qt knows about the argument types are their name as a string, extracted by moc.

QMetaType provides a way from the string (e.g. "QPoint") to get a to copy or destroy the object. Qt would then use void *QMetaType::create(int type, void *copy) and QMetaType::destroy(int type, void *data) to copy and destroy the arguments, where the int type is obtained using QMetaType::type(const char *typeName) using the type name of the argument, as provided by moc. QMetaType also provides a way for the developer to register any kind of type in the meta type database.

Another use case for QMetaType is QVariant. The QVariant from Qt 3.x only supported built-in Qt types because a contained arbitrary type would also need to be copied or destroyed together with the wrapping QVariant. But with the help of QMetaType, QVariant was able to contain any registered type since QVariant can now copy and destroy contained instances of objects.

What information does QMetaType keep?

Since Qt 4.0 a lot has changed. We now have QtScript and QML which are making intensive use of the dynamic type integration. And we had to optimize a lot.

Here is the list of information kept for each type in the meta-type system:

  • The Type Name as registered. There is a name index for fast lookup of the meta type id. Since Qt 4.7, it is even possible to register the same type with different names (useful for typedefs).
  • (Copy) Constructor and Destructor (in-place or not).
  • Size to know how much space to allocate for a stack or inline member construction.
  • Flags specifying the same information as QTypeInfo (see bellow) or the type of conversion.
  • Custom conversion functions, set by QMetaType::registerConverter.
  • QMetaObject, containing the meta QObject data associated with a type if it exists.

QTypeInfo

QTypeInfo is a trait class orthogonal to QMetaType, it allows the developer to manually specify (using the Q_DECLARE_TYPEINFO) that a type is movable (using memmove) or if its constructor/destructor need to be run. This is mainly used for optimization in containers like QVector.

For example, the implicitly shared classes may be moved with memmove. While a normal copy should first increase the reference count with the copy constructor and then decrease it in the destructor.

C++11 introduces move constructors and standard type traits to solve this problem, but since QTypeInfo was designed way before C++11 and Qt still has to work with older compiler, we have to do without.

How does it work?

For historical reason, there is a big difference between built-in types and custom types. For built-ins types in QtCore, each meta-type function is basically a switch that has special code for each type. In Qt 5.0 this was re-factored to use templates a lot. (See QMetaTypeSwitcher.) But what is going to interest us in this article is how it works for custom registered types.

There is simply a QVector<QCustomTypeInfo> that holds all the information and a bunch of function pointer.

The Q_DECLARE_METATYPE macro.

That macro specializes the template class QMetaTypeId for the specific type. (In fact, it actually specializes the class QMetaTypeId2 and most of the code uses QMetaTypeId2. I don't know the exact reason behind QMetaTypeId2. Maybe so that Qt can add more built-in types without breaking code that used Q_DECLARE_METATYPE before.)

QMetaTypeId is used to determine the meta-type id at compile time for a type.
QMetaTypeId::qt_metatype_id is the function called by the qMetaType<T>(). On the first call of this function, it will call to some internal function within QMetaType to register and allocate a meta-type id for this type, using the name specified in the macro. It will then store that id in a static variable.

Apart from the name, all other information is automatically inferred by the compiler using templates.

qRegisterMetaType

Type registered using Q_DECLARE_METATYPE are going to be actually registered (and be assigned an id) on the first use of qMetaTypeId(). That's the case when a type is wrapped in a QVariant for example. But this is not yet registered when connecting signals and slots. In that case you need to force the first use using qRegisterMetaType

Automatic registration.

Developers often forget to register their meta-type until they see the compilation error or run-time error telling them to do so. But wouldn't it be nice if it would not be necessary? The only reason Q_DECLARE_METATYPE is necessary is to get the name. But there are cases where we can know the name at run-time without that macro. For example, QList<T> if T is already registered, we can query the meta-type system and construct the name using "QList<" + QMetaType::name(qMetaTypeId<T>()) + ">"
We do that for a bunch of templated classes, for example: QList, QVector, QSharedPointer, QPointer, QMap, QHash, ...
We can also determine the name of a pointer to a QObject subclass thanks to the information provided by moc: T::staticMetaObject.className() + "*"
And from Qt 5.5, we will also automatically declare the Q_GADGET and Q_ENUM.

That was it for Q_DECLARE_METATYPE, but you would still need to call qRegisterMetaType to use these type in a Q_PROPERTY or as a parameter in a signal/slot queued connection. Since Qt 5.x however, the code generated by moc will call qRegisterMetaType for you if moc can determine that the type may be registered as a meta-type.

Research

Before Qt 5.0, I was trying to investigate if we would not be able to get rid of Q_DECLARE_METATYPE for cases in which we do not need the name. This worked somehow like this:

 
template<typename T> QMetaTypeId {
    static int qt_metatype_id() {
        static int typeId = QMetaType::registerMetaType(/*...*/);
        return typeId;
    }
};

According to the C++ standard, there shall be exactly one instance of the variable QMetaTypeId::qt_metatype_id()::typeId for each type. But in practice some compilers or linkers do not obey this rule. In particular, on Windows, there would be one instance per library even when using the proper export macro. We would therefore always need a name identifier which we don't have. (And we don't want to rely on RTTI). Therefore we only register the type for which we can know the name in Qt 5.

Woboq is a software company that specializes in development and consulting around Qt and C++. Hire us!

If you like this blog and want to read similar articles, consider subscribing via our RSS feed, by e-mail or follow us on twitter or add us on G+.

Submit on reddit Submit on reddit Tweet about it Share on Facebook Post on Google+

Article posted by Olivier Goffart on 22 April 2015

Get notified when we post a new interesting article!

© 2016 Woboq GmbH