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.
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.
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
|Usage||Convenient macros||Ugly macros||Ugly macros|
|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|
W_OBJECT(MyClass) ... W_OBJECT_IMPL(MyClass)
public slots: mySlot(int x);
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)
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.
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.
The best way to learn about how to use Verdigris is to read through the tutorial (conveniently brought to you through ourCode Browser).
All benchmarks were done with CopperSpice 1.2.2, Qt 5.6.0 or Qt 4.8.3, GCC 6.1
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
|Qt 5 (moc)||Verdigris||CopperSpice|
|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)|
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.
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
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 .