venerdì 18 maggio 2012

From G to Q

I decided to blog about my experience of transitioning from being a GLib and GObject enthusiast to a Qt (and QML) fanboy. :-) I'm not writing this with any specific goals, other than sharing my own experience and hopefully making the life of those developers who are forced to learn and use Qt less painful and a bit more rosy; in particular, I'm not trying to be a Qt evangelist here and convince you, hardcore GObject developer, to switch to Qt: I'm indeed sure that if my current self tried to talk my 3 years younger self into Qt, it would be a useless and tiresome (and maybe violent ;-)) discussion.

Just a bit of background on myself: I've been developing on Linux and Unix for more than 10 years now, mostly using GObject based libraries, such as Gtk+. I had a short encounter with Qt in about 2004, just for a few weeks, which left me with a bad impression (mostly because of the uic). Other than that, I was simply not interested in Qt, mostly because I hate C++ and love C and anyway all my needs were perfectly met by GLib, GObject and Gtk+. I remember that the first time when I read about GObject, I was fascinated: the power of OO programming on top of my favorite language, all open source and hackable. In autumn 2006 I started a new job in Nokia, in the maemo team, working on a middleware component in the Telepathy VoIP framework; I enjoyed it a lot, and (if possible) grew even more fond of GObject and GLib. On early 2008, the big news: Nokia acquires Trolltech (the SW house developing Qt); for me, as for most of the people working in the maemo team, this came as a complete surprise, and not as a really good one. Indeed, we were afraid that we would have to abandon the C/Gtk+ work and switch to C++/Qt — perhaps not coincidentally, several GNOME developers working in Nokia even left the company around that time. Luckily though, nothing really changed for us for more than one year, and even later, when more people joined to work on Qt-related stuff and I moved to another project, I could still avoid touching Qt code and continue using only my beloved GObject. However, like all good dreams, this also came to an end: eventually, I had to refresh my C++ knowledge and change my most typed character from "g" to the hated "q".
So, here my Q story begins.

The first prolonged exposure to Qt

It was painful. My first task was to create Qt-bindings for the GLib-based library I was developing. There was no UI involved, so on the tools front the experience was not as bad as I remembered; but on the pure programming side, I was horrified: I felt extremely limited, many programming paradigms I was used too couldn't be applied, or I was just told (by people more expert in Qt) that “you shouldn't do like that, it's not the Qt way!”. Urgh.

The biggest issues I had were the lack of reference counting in QObject (!!) and the veto on using C-style function callbacks for asynchronous operations (you should use Qt slots, and in order to have a slot you must have a receiving QObject). How could I take Qt seriously? Sure, there were also a few nice things about it, like the fact that QObject comes with child-parent lifetime handling out of the box (when a parent object dies, all of its children are automatically deleted), and that signal connections are automatically broken when either the emitter or the receiver object dies (yay, no more explicit signal disconnections — or random crashes!). But still, this could in no way compensate for the lack of the other low-level features I was used to.

I cannot really say that I enjoyed the first months of using Qt. I was still trying to work as much as possible on the GLib-based parts of the project, and twisting my head around to figure out what was “the Qt way” of doing all the things that I was used to do almost instinctively with GLib/GObject.

From hate to love

Now that I think back and try to find the moment of time when I finally started liking Qt (or just not dislike it too much), I cannot really point out a definite time. It was a very slow process, that surely wouldn't have completed if I hadn't been forced to use Qt because of my work. But indeed, the moment when I realized that Qt was not so bad after all is when I started writing new original code with it. In retrospection, I would say that my experience with Qt was so bad because I started with a completely wrong task: as an inexperienced Qt developer, creating binding for a GObject-based library couldn't possibly make me appreciate Qt; I was just bound to notice all the GObject features that were not present in Qt, and suffer their absence. And I wouldn't notice almost any of the niceties that Qt offers to application developers, because there is not much code you can write in a wrapper library other than some trivial glue code.
So, my first suggestion for those GObject developers who start using Qt is: do not write a Qt wrapper to your favorite GObject library!, you are going to hate it. Leave it to someone who is more experienced than you in Qt; it can be a very pedagogic task, indeed, but chances are that it will be the last time you are going to use Qt. If you can, start with writing new code.

That said, there is a big difference between not disliking something and loving it. But again, I cannot tell when I started loving Qt. What I surely can tell is when I realized that I loved it: and that was when I started porting some GObject-based code to Qt (not as a line-by-line port, but more like a functional rewrite) and found out that the resulting code was not only more compact and easier to write, but — and especially — easier to read. And of course, when I realized that my enthusiasm was not the same as before, whenever I had to do some programming in C+GObject.

What I find cute in Qt

Let me begin with a couple of statements which could dispel some myths or anyway clarify some doubts that the Qt-virgin might hold:

  • It's not C++. This is a bold (and false) statement, but it's probably the most effective to convey the idea: if you — like me — hate C++, you won't necessarily hate Qt. Qt's C++ is a subset of C++: although you can use whatever C++ feature you might like, developing with Qt does not involve using exceptions, templates, RTTI and the STL. In a similar way to how GLib and GObject “extend” the C language, Qt uses a preprocessor (called moc) to add its own run-time type information (think of GType) and implement the signals/slots mechanism. In the end, you don't need to know all the details of C++ to develop Qt; instead, it's somehow similar to developing Vala code — it's like a programming language of its own.
  • It's not just GUI. As you might have noticed, this post is not about Qt vs. Gtk+, not at all. There are many other articles on the web where you can find comparisons and opinions of the two, but what I'm focusing on here is my experience with Qt programming and GNOME programming, and not just at the UI level. So, yes, the point is that you can develop whatever code with Qt (except maybe kernel drivers): I've personally written D-Bus services, command line tools and embedded software using just Qt.
  • There really is a Qt way. My disappointment about the lack of reference counting in QObject is now history: apart from the fact that indeed there is one class in Qt which lets you implement reference counting for QObjects, I cannot recall any case where reference counting would have been useful. I know, coming from GObject this might sound hard to believe (especially to my three years younger self, who used to mess up with cross references everywhere in the code), but it's a fact: after some initial struggle, I always managed to design the software with clear ownership of objects, so that the reference counting ended up to be totally superfluous. And in fact, my code is designed better now. The same can be said of any other feature that I initially missed: if it's not there, you can do without it: just restructure your code in a different way — the Qt way — and there's a good chance that while reworking your classes you'll clear up some mess. :-)

That said, here's a list of things that I came to like about Qt; I'm obviously not mentioning those things that are also in GLib/GObject, unless there are significant differences.

  • QObject automatic destruction of children. I already mentioned it before, but it's worth expanding a bit. Quite often I had to use reference counting in my GObject subclasses to keep track of the child-parent relationships; QObject already does that for you: whenever you create a QObject, you can specify one parent object which will “watch” its lifetime and destroy it in its destructor. This leads to a nice programming paradigm where you can create all the objects that your class needs and just use the this pointer as parent object: then you don't need to remember destroying them explicitly in your destructor.
  • Automatic disconnection of signals when receiver object dies. I also mentioned this before, but repetita iuvant: if you are an experienced C + GObject developer, you must have seen at least once in your programming career one crash due to a signal callback invoked when the objects it operated on had already been destroyed. Of course I know that it never happened to you personally, but you surely saw this in someone else's software. ;-) Well, in Qt this is very unlikely to happen, because (at least till Qt4) it's impossible to bind signals to C callbacks: you must bind them to a QObject method (called “slot”), and the QObject system takes care of disconnecting the signal for you when either the emitter or the receiver object are destroyed.
  • QString. I'm sometimes a bit maniacal with micro-optimizations, and one thing I was always trying to optimize in my early GLib days was avoiding string allocations. It really hurts me to write a g_strdup() in a library method's implementation when I could rather write "Please, don't destroy or change @string_param while this object is still alive" in the documentation string for the method. But that's not the GLib way (and indeed, not my way anymore when I write GLib code): you can never know what happens with the string, so it's way safer to incur the small penalty of a g_strdup() than run the risk of a crash which is likely to be very hard to debug. So, C/GLib code is often full with g_strdup() and g_free() (not to speak about valac-generated code!). Enter QString! QString is not only a class that provides a ton of useful string methods, it is an implicitly shared class. That means that the QString structure itself is very small (size of a pointer?), and that the string data is reference counted; then if/when you modify a string which has a reference count higher than 1, the string data is copied and your QString is now pointing at the modified copy. So you never have to worry about copying strings! How cool is that? :-)
  • Awesome container classes. Qt comes with a few container classes which you'll surely get to like: QMap, QList are those that you'll use the most, but then there's also QSet, QQueue, QHash, QVector and others. These are template classes, so you can write QMap<QString,QVariant> and get the equivalent of a GHashtable whose keys are strings and values are GValues. Inserting items is as easy as writing
    QMap<QString,QVariant> map;
    map.insert("string", "string value");
    map.insert("integer", 12);
    
    while to iterate the values you have the handy foreach macro, which lets you write
    foreach (const QVariant &value, map) {
        QDebug() << "Value:" << value;
    }
    
    And if you remember what I wrote above about QString: Qt container classes are implicitly shared too!
  • Reduced usage of pointers. This follows in part from the above: QString handles all string pointers for you, and the ease of implementing implicit sharing of container classes makes it so that you will be often passing them by value or by reference. Also, implementing implicit sharing for your own data structures is very easy, and you might prefer investing a little time doing that rather than using raw pointers.
  • Class and API offering. Even excluding the GUI classes, the functionality provided by the Qt classes will satisfy the most demanding developer: whether you want to deal with date/time, with network resources, with graphics/images, HTML (webkit), SQL databases, D-Bus, plugins, regular expressions, etc., it's all there, and everything works perfectly together. The APIs are intuitive and clean: you won't find duplicate classes offering the same functionality (unless one is deprecated), and the documentation is excellent.
  • Compact and clean code. Qt application code is likely to be much mode compact and clean than GObject code. Just like Vala, imagine removing all GObject casts and using class namespaces to shorten your method names and fit your code in a 80 chars wide editor. :-) Use const effectively to mark your class methods and finally be able to pass constant objects and structures around your code, minimizing the risk of incorrect usage.
  • QML. This would deserve a chapter on its own, but since there has been quite a lot of buzz around it on the internet I won't spend many words on describing it. Instead, I'll focus on how I came to like it so much. As it often happens with new things, my first impression was not so great; sure, all the QML demos you can find in the internet are extremely captivating, and it's impressive how much you can achieve with very little code — which doesn't even look like real code, but more like a static description. And this was indeed my first doubt: what is the limit of QML? It's a nice language for moving graphics items around, but does it let me do anything more than that? Or: if it really allows me to write complex UIs for big applications, won't the code become a horrible mess to maintain? Again, time and hands-on experience is what changed my mind. I'm porting one map application from Gtk+/Clutter to Qt and of course the map widget and its animations has been my first item in the list. Clutter is a fantastic toolkit for producing animated UIs in the GNOME world; the Qt equivalent is QGraphicsView, whose APIs luckily match very close the Clutter ones; so, the porting wasn't that difficult at all, and it didn't took long until I had my widget working. However, for implementing the animations (zoom, panning and rotation of the map) I didn't use the Qt Animation Framework, which would be the natural equivalent to Clutter animation classes, but instead went for QML: exposing the map properties to QML enables implementing animations in a much easier and enjoyable way. QML property bindings, together with property animation descriptions, empower your application to any kind of smooth animations with almost no effort on your side. You don't have to take care of starting/stopping animations, changing the direction of animations while they are running, or combining zooming/panning/rotation at overlapping moments: no matter how crazily the user plays with your UI, all animations happen as expected, without any need for you to special code them. I couldn't help falling in love with QML. :-)
    So, my answers to the above questions are that, first of all, QML is a language especially designed for developing UIs and UI logics. There's not much else that you can do beside that; in order to write algorithms and implement the rest of application (all what is non-UI) you have to resort to either Javascript or C++ (or Python, etc.); the only difference on how you use these languages is that it's possible to write the Javascript code in the .qml file itself, which is very convenient but also, in my humble opinion, something that you shouldn't do. So, while you could say that it is possible to write an entire application in QML, I heartily discourage you from doing so; if you really like Javascript (why would you, by the way?) and want to write your application with it and QML, it's much better to write the code of your application logic in a .js file and import that from QML. About QML code becoming a maze when writing complex applications, I disagree; of course, it's possible, but it's the same in all other languages (which I know of). If you split your code into smaller QML files (per UI component or groups of components) and avoid creating cross dependencies between components, everything will go smoothly — my golden rule when writing QML code is that a .qml file should never refer to identifiers which are not defined in the file itself. The reason why I'm getting so down in technicalities here is that, if you are a developer who has some experience with QML but finds it messy, you should go and check your code against this rule. :-) Indeed, QML is not a panacea which let everyone who is not a developer write über-complex applications; it's easier and more effective than all other languages (which I know of, again), but the rules of good software development still apply.

How you can start liking Qt

No way you will. ;-) Joke aside, there is some truth in that answer: first of all, you will never like Qt until you try it; and you won't try it unless you are forced to, or have some very good reasons to start looking into it. In my case, in fact, I was forced to, and — at that time — I had absolutely no reason to try Qt on my own: I already had some bad memories with it, I didn't like KDE, the skype Linux client and, most importantly, I was totally happy and satisfied with coding C and GObject.

However, I believe that nowadays there might be two reasons why even a hardcore GNOME developer could, in an unforgivable moment of weakness, decide to try programming with Qt: QML and application portability. QML is the new cool stuff (it was not there when I started with Qt), there are plenty of nice demos and examples out there, and they do pretty amazing things. Especially given that setting up the environment is an easy task (just install the Qt packages from your Linux distribution) and running the demos is in most cases just a matter of typing qmlviewer <file>.qml, and that you are a very curious geek, it's not so unlikely that you'll want to have a closer look to the demos; and since the source code consists in most cases of just a few lines of JSON-like text, it won't take long for you to understand it and maybe start playing with it. Especially because I still believe that GNOME developers deserve something as cool as QML. :-)
The other reason why trying Qt nowadays is not so absurd as it might seem is portability. Yes, Qt has always made portability his flag, but who does really care about Windows and Mac? However, what you might care about is Android! What has changed here in the last couple of years is that Qt and QML run on Android, with GL acceleration and multitouch support, enabling you to develop modern applications which can be run on millions of devices; you can also target the best smartphone ever, Symbian smartphones and whatever devices Nokia will come up with for the next billion.

I don't think there is much more I can write to stimulate you to try out Qt or, if you have already started using it, to support your efforts to master it. What I can tell you, with no fear of being contradicted, is that eventually you will like it; there is no way that this won't happen, if you continue learning it. At some point you will realize that going back to C (or even Vala) and GObject is not going to make your programming more productive, or even more enjoyable than how it has become with Qt. It's just a matter of enduring the difficulties and unpleasantness for a long enough time; which can be even several full months if you are, like me, one who takes technologies, programming languages and libraries next to your heart.

Etichette: , , , ,

9 Commenti:

Blogger ocrete ha detto...

About the automatic disconnection of signals, in GObject, you have g_signal_connect_object() which does exactly that.

24 maggio 2012 21:07  
Blogger ocrete ha detto...

Also, with GObject, you can do the parent/child model too, GtkObject and GstObject do just that.

24 maggio 2012 21:08  
Blogger Alberto Mardegan ha detto...

Questo commento è stato eliminato dall'autore.

25 maggio 2012 09:06  
Blogger Alberto Mardegan ha detto...

Olivier, I know about g_signal_connect_object(); it has always been there, but always (partially) broken. Or at least, that's what the GLib documentation has led me (and many others to believe). The bug has 10-years long story, and I don't think it's a good sign of how the project works: https://bugzilla.gnome.org/show_bug.cgi?id=118536

About the parent/child model, it's indeed possible to implement it in GObject (and that's what I've been doing all the time); I was just pointing out that it's not in GObject itself, so everyone has to re-invent it.

25 maggio 2012 09:08  
Blogger Shmerl ha detto...

Actually I'd say, reimplementing what STL does (vector, map and etc.) is not a positive thing. Unless it really offers some performance advantage over the standard containers. Many historic toolkits used to do it from the times when STL wasn't mature. Today it's really not a good thing to do.

I do like C++ in general, and I don't agree that Qt is not C++. It could probably use more of the C++ deeper features, but it's still C++.

7 giugno 2012 00:16  
Blogger Unknown ha detto...

Now this one was a good read :D
Luckily I've never been exposed to GObject and started right away with Qt.

Btw, I don't really get how you could dislike C++ or javascript, both are awesome :)
And personally I hate with object oriented C code with a passion, it looks so unpolished!
Guess tastes are tastes :)

The only sad note is not seeing Qt on the new nokia windows-based flagships :(

7 giugno 2012 03:15  
Anonymous Robert ha detto...

I must say this echos my experiences strongly. I was a big C++ skeptic when I started Qt programming, and looking back gobject programming seems like a nightmare.

"Actually I'd say, reimplementing what STL does (vector, map and etc.) is not a positive thing. Unless it really offers some performance advantage over the standard containers."

Features.

e.g. implicit sharing, as noted in the article

7 giugno 2012 17:33  
Blogger Shmerl ha detto...

No one is forcing you to use raw pointers. You can use smart pointers (like in boost or in C++11), and implement all the sharing and reference counting when needed. So again, no need to reinvent the wheel when there are standard methods.

I don't criticize Qt features duplication per se - Qt did it out of necessity, since such kind of things entered C++ lately (with C++11). But boost shared pointers were around for quite a while already.

7 giugno 2012 18:54  
Anonymous Anonimo ha detto...

It's rather to keep consistent API, better code integration and doing things in Qt way.

Other smart pointers? There is something about them: http://blog.qt.digia.com/blog/2009/08/25/count-with-me-how-many-smart-pointer-classes-does-qt-have/

So once again, they do whole Qt in Qt way ;]

29 gennaio 2013 01:46  

Posta un commento

Iscriviti a Commenti sul post [Atom]

<< Home page