📜 ⬆️ ⬇️

Dispelling the myths about the Qt meta-object compiler

I often meet criticism of the Qt framework, in which he is accused of using the meta-object compiler (the moc utility). As one of the moc developers, I decided to write this article in order to debunk some of the associated myths.

Introduction


Moc is one of the developer tools and part of the Qt library . Its task is to support the extension of the C ++ language, which is necessary for introspection and reflection in Qt (this includes signals, slots, and QML). For a more detailed explanation, you can read about how signals and slots in Qt work .

The need to use moc is one of the main targets of Qt's criticism. This even led to the appearance of forks in Qt, which fundamentally refused moc (for example, CopperSpice). But still, most of the so-called weaknesses attributed to moc are not justified.

Myths


Moc rewrites your code before passing it to the compiler.


This is a common misconception. Moc does not modify or rewrite your code. It simply parses a piece of code in order to generate additional C ++ files, which will then be compiled independently. This is not a very big difference, but still an important technical misunderstanding.
')
Moc simply automatically generates a template code that could be long and tedious to write manually if moc did not exist. If you are a masochist, it is still possible to take and write all the tables for introspection and realization of signals. Well, or rely on a reliable tool that will do it for you.

Using Qt, you do not write in real C ++


I have heard this argument many times, but it is simply incorrect. The macros used by moc for code annotation are the standard C ++ macros. They must be correctly recognized by any tool that can analyze C ++ code. When you add Q_OBJECT to your code, you simply append the declaration of several functions . When you write “signals:”, you simply add a macro, which will turn into “public:”. Many other Qt macros do not expand at all. Moc simply finds them and generates a code of signal emitters and introspection tables.

The fact that your code can now be read by another tool does not make it “less conforming to the C ++ standard”. You do not think the code written with the expectation of using gettext or doxygen is any kind of “less correct C ++”?

Moc complicates code building process


If you use any industrial code building system, like CMake or qmake, then you get native support for Qt. Even with some kind of proprietary build system, we are talking about just one additional start of the header file processing command. All the build systems I know allow you to add steps to run additional commands before running the compiler, since many projects in one form or another use code generation when building a project. Recall, for example, tools like yacc / bison, gperf , llvm / TableGen .

Moc makes debugging harder


Since moc generates code in pure C ++, debuggers should not have any problems with it. We try to keep the generated code in such a state that it does not trigger compiler warnings or static or dynamic code analysis tools. Yes, sometimes when debugging, you will see traces of the generated moc code in the Cluster. In some rare cases, you may get an error related to the code created by moc, but usually the reasons are fairly easy to detect. The code generated by moc is quite human readable. It is also likely to be understood and debugged even easier than the infamous errors of some libraries built on advanced use of templates.

Rejecting moc improves performance during code execution.


This is a direct quote from the CopperSpice home page, and is probably their biggest lie. The code generated by moc tries to avoid dynamic allocations and reduce the number of reallocations of memory. The generated moc tables are constant arrays and are stored in the read-only data segment. CopperSpice registers its QMetaObject (information about signals, slots and properties) at runtime.

Milian Wolff did some performance comparisons of Qt and CopperSpice in his report on CppCon2015. Here is a screenshot of one of his slides (less is better).

image

It should also be noted that the code on Qt, even with the launch of the moc compiles faster than the code on CopperSpice.

Outdated myths


Some criticism was once fair, but more is not.

The macro cannot be used when declaring a signal, slot, base object class, and also ...


Before the release of Qt5, the moc utility didn’t open macros. But since Qt 5.0, moc fully supports macros in all the places listed above, so this is no longer a problem at all.

Enumerations (enums) and type overrides (typedefs) must strictly conform when used as parameters of signals and slots


This is only a problem if you still want to use string-based connection syntax (since it does use direct comparison of type names). With the release of Qt5 and the new syntax, this is no longer an obstacle.

Q_PROPERTY does not allow commas in types


Q_PROPERTY is a macro with one argument that is not expanded and serves only to help moc. But since this is still a macro, a comma in, for example, QMap <Foo, Bar> separates the macro arguments and causes a compilation error. When I saw CopperSpice using this argument against Qt, it took 5 minutes to fix it using the variadic macros from the C ++ 11 standard.

Other criticism


Templates, nested classes, or classes used by multiple inheritance cannot be QObjects


Although this is true, these features are not yet supported by QObject, although they can be fully implemented in moc if we want to. The Qt project does not currently consider these features as priorities.

I once added support for template QObjects in moc, but this change was not included in the main development branch, since no one else expressed interest in this functionality.

You also need to note the support for template and nested classes in moc-ng .

Multiple inheritance is in itself very ambiguous. Most often, it indicates problems with the architecture of the application and many modern languages ​​directly prohibit it. You can still use multiple inheritance with Qt if the QObject is the first base class in the inheritance chain. This small limitation allows us to apply useful optimizations. Ever wondered why qobject_cast is so much faster than dynamic_cast?

findings


I don't think moc is the problem. Qt macros really help to implement the necessary Qt functionality. Comparing them with the CopperSpice approach, we can notice in the latter a significant redundancy of the service code, as well as the unfriendly syntax of macros (not to mention the performance loss at runtime). The syntax of signals and slots that has existed in Qt since the 90s is one of the fundamental things that ensured the success of the framework.

You may also be interested in exploring some experiments related to moc, like moc-ng (this is moc, rewritten using clang libraries). There is also this study of replacing moc using the C ++ reflection tools. Well, Verdigris library, with macros that create QMetaObject without moc.

Source: https://habr.com/ru/post/327176/


All Articles