Verdigris: Qt without moc
Verdigris is a header-only library that can be used with Qt. It uses macros to create a QMetaObject that is binary compatible with Qt's own QMetaObject without requiring moc. In other words, you can use Verdigris macros in your Qt or QML application instead of some of the Qt macros and then you do not need to run moc.
TL;DR: Github repository - Tutorial
Introduction
CopperSpice is a fork of Qt 4. Its main raison d'être is to get rid of moc because they consider it bad enough (IMHO wrongly). To do so, they replaced the user-friendly Qt macro with some less friendly macros.
However, CopperSpice is a whole fork of Qt, meaning they are maintaining the whole library. This also means they are recreating an ecosystem from scratch. If they had made it binary compatible, then they could have removed moc without the need to maintain the full Qt library. This is what Verdigris is.
Another problem of CopperSpice compared to Qt is that it generates and registers the QMetaObject
at run-time when loading the application. Meanwhile, Verdigris uses constexpr
to generate
the QMetaObject at compile time. For this reason, binaries using CopperSpice are much bigger
than binaries using Qt (moc or Vedrigris), and take also more time to load because of the massive
amount of relocations.
Previous work
Most of the ground work is based on the code I wrote already in my previous blog post: Can Qt's moc be replaced by C++ reflection?. In that blog post, I was trying to see if reflection could help replace moc, while keeping the convenience of the current Qt macros. The goal was was to influence source compatibility as little as possible.
CopperSpice decided to use different macros that are less convenient. The macros of Verdigris are based or improved upon the CopperSpice ones.
Differences between CopperSpice and Verdigris
Pure Qt | Verdigris | CopperSpice | |
---|---|---|---|
Requires moc | Yes | No | No |
Usage | Convenient macros | Ugly macros | Ugly macros |
Qt Compatibility | Obviously | Yes | No |
MetaObject generation | Compile Time (By moc) |
Compile Time (By the compiler) |
Run-time (At load time) |
MetaObject location | Shared read-only memory | Shared read-only memory | Heap |
Macros
Qt | Verdigris | CopperSpice |
---|---|---|
Q_OBJECT
|
W_OBJECT(MyClass)
|
CS_OBJECT(MyClass)
|
public slots:
|
void mySlot(int x);
|
CS_SLOT_1(Public, void mySlot(int x))
|
signals:
|
void mySignal(int x)
|
CS_SIGNAL_1(Public, void mySignal(int x))
|
Q_PROPERTY(int myProperty
|
W_PROPERTY(int, myProperty
|
CS_PROPERTY_READ(myProperty, getProp)
|
private slots:
|
void myPrivateSlot(int x);
|
CS_SLOT_1(Private, void myPrivateSlot(int x))
|
The first difference of Verdigris is the
W_OBJECT_IMPL
macro that needs to be written in the .cpp file.
This is one of the few points for which Verdigris is less convenient than CopperSpice as they do not need
this macro.
In CopperSpice, you cannot define a slot inline in the class definition. You don't have this restriction
with Verdigris.(Update: I was told it is possible with CopperSpice by putting the body within the CS_SLOT_1
macro)
Both CopperSpice and Verdigirs can have templated QObject class or nested QObject.
Verdigris cannot, however, have function local QObjects (because of the static member
staticMetaObject
) and local classes cannot have static members.
From an implementation point of view, CopperSpice macros use __LINE__
to build an unique identifier,
which means that two macros cannot be put on the same lines. So you can't declare several slots in a line
or declare properties or signals/slots from a macro. (which ironically is one of the "problems" they raised about Qt4's moc).
Verdigris's macros do not have this problem.
Tutorial
The best way to learn about how to use Verdigris is to read through the tutorial (conveniently brought to you through our Code Browser).
Benchmarks
All benchmarks were done with CopperSpice 1.2.2, Qt 5.6.0 or Qt 4.8.3, GCC 6.1
KitchenSink
I made the KitchenSink example from CopperSpice compile both with CopperSpice, Qt 5 with moc
or with Verdigris
(patch).
This table show the amount in minutes:seconds taken by make -j1
Qt 5 (moc) | Verdigris | CopperSpice | |
---|---|---|---|
Compilation time | 1:57 | 1:26 | 16:43 |
Binary size | 1.32 MB | 1.36 MB | 115 MB |
I was surprised to see that Verdigris compiles faster than using moc.
The cost of compiling the generated code in a separate compilation unit is what makes it slower, and
including the generated file is a common way to speed up the compilation (which was not done in this case).
CopperSpice is probably so slow because each translation unit needs to re-generate the code that generates
the meta object for all the included objects (including the headers from CsCore, CsGui, ...).
Verdigris, however, moves most of the slow-to-compile code in a
W_OBJECT_IMPL
macro in the .cpp code
that is only parsed for the corresponding translation unit.
Still, the tests take a very long time to compile, that's because they
have many objects with lots of special methods/properties and we are probably hitting non-linear
algorithms within the compiler.
Library loading time
Any program that links to a C++ library has some overhead because of the relocations and the init section. This benchmark simply links an almost empty program with the libraries, and compute the time it takes to run.
CopperSpice (CsCore, CsGui) |
Qt 4 (QtCore, QtGui) |
Qt 5 (Qt5Core, Qt5Gui, Qt5Widgets) |
---|---|---|
56ms | 16ms | 17ms |
Loading CopperSpice is much slower because all the MetaObjects needs to
be created and the huge amount of relocations.
Note: Since the program is empty and has no QObject on his own, neither moc nor Verdigris were used.
Qt5 and Qt4 themselves were compiled with moc as usual.
Signals/Slots run-time performance
I built the Qt benchmarks for testing connecting and emitting of Qt signals. There is no difference between Verdigris or the normal Qt. As expected since the QMetaObject structure is the same and the moc generated code is equivalent to the templated code.
CopperSpice is faster for signal emission because they inlined everything including
QMetaObject::activate
. Something we don't do in Qt because we want to maintain binary
compatibility and inlining everything means we can't change much of the data structures used until
the next major version of Qt. This contributes largely to the code size which is two order
of magnitude more than using Qt.
Implementations details
As said previously, most of the constexpr code was based on what I wrote for the previous research.
I had to replace std::tuple
with another data structure because std::tuple
turned out to be way too slow to compile. The GNU's libstdc++ implementation of std::tuple
does about 16 template recursion per parameter only
to compute the noexpect clause of its copy constructor. Which is quite limiting when the default limit
of template recursion is 256 (so that means maximum 16 types in a tuple). There is also the fact that
the compiler seems to have operation of quadratic complexity depending on the amount of template parameter
or instantiation. I therefore made my own binary tree structure that can compile
in a reasonable time. Instead of having tuple<T1, T2, T3, T4, T5, ....>
we have
Node<Node<Node<Leaf<T1>,Leaf<T2>>,Node<Leaf<T3>,Leaf<T4>>>, ...>
Nonetheless, the whole thing is some pretty heavy advanced template and macro tricks. While working on it I found and reported or even fixed compiler bugs in clang [1], [2], [3], and GCC [1], [2], [3]
Conclusions
In conclusion, as CopperSpice has already shown, this shows that moc is not strictly necessary for Qt features. The trade-off is the simplicity of the macros. The alternative macros from CopperSpice and Verdigris are less convenient, and force you to repeat yourself. Complex template code can also increase compilation time even more than the overhead of moc.
On the other hand, we think the approach of CopperSpice was the wrong one. By forking Qt instead of being compatible with it, they give up on the whole Qt ecosystem, and the small CopperSpice team will never be able to keep up with the improvements that are made within Qt. (CopperSpice is a fork of Qt 4, it is way behind what Qt 5 now has)
Verdigris is a header-only library consisting on two headers that can easily be imported in a project to be used by anyone who has reasons not to use moc. You can get the files from the Github repository.
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 (Via Google Feedburner, Privacy Policy), by e-mail (Via Google Feedburner, Privacy Policy) or follow us on twitter or add us on G+.
Article posted by Olivier Goffart on 25 May 2016
Click to subscribe via RSS or e-mail on Google Feedburner. (external service).
Click for the privacy policy of Google Feedburner.
Google Analytics Tracking Opt-Out
Loading comments embeds an external widget from disqus.com.
Check disqus privacy policy for more information.