Est. 2011

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.

A verdigris statue

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)
...
W_OBJECT_IMPL(MyClass)
CS_OBJECT(MyClass)
public slots:
   void mySlot(int x);
void mySlot(int x);
W_SLOT(mySlot)
CS_SLOT_1(Public, void mySlot(int x))
CS_SLOT_2(mySlot)
signals:
   void mySignal(int x);
void mySignal(int x)
W_SIGNAL(mySignal,x)
CS_SIGNAL_1(Public, void mySignal(int x))
CS_SIGNAL_2(mySignal,x)
Q_PROPERTY(int myProperty
   WRITE setProp
   READ getProp
   NOTIFY propChanged)
W_PROPERTY(int, myProperty
   WRITE setProp
   READ getProp
   NOTIFY propChanged)
CS_PROPERTY_READ(myProperty, getProp)
CS_PROPERTY_WRITE(myProperty, setProp)
CS_PROPERTY_NOTIFY(myProperty, propertyChanged)
private slots:
   myPrivateSlot(int x);
void myPrivateSlot(int x);
W_SLOT(myPrivateSlot, (int), W_Access::Private)
CS_SLOT_1(Private, void myPrivateSlot(int x))
CS_SLOT_OVERLOAD(myPrivateSlot,(int))

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+.

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

Article posted by Olivier Goffart on 25 May 2016

Load Comments...
Loading comments embeds an external widget from disqus.com.
Check disqus privacy policy for more information.
Get notified when we post a new interesting article!

Click to subscribe via RSS or e-mail on Google Feedburner. (external service).

Click for the privacy policy of Google Feedburner.
© 2011-2023 Woboq GmbH
Google Analytics Tracking Opt-Out