EDITING BOARD
RO
EN
×
▼ BROWSE ISSUES ▼
Issue 23

Qt: How I Came To Love C++

Ambrus Oszk
Software Engineer
@Accenture Romania
PROGRAMMING

After so many nerves lost over all the quirks and twists and turns of C++ with OpenGL and MFC, working with C# or Java was like a breath of fresh air. But on a day of Irish rain and wind, in a dim cubicle in a corner of an office, along came Qt and a wonderful C++ world opened up, with new revelations ever since. After moving over to Qt, working with C++ became a joy again, and it is one of the environments I most dearly work in. Development is quite an experience, everything remaining native, fast and easily maintainable in the same time.

Why Qt?

, flexibility comes at a great price in complexity overhead and a plethora of pointer-related problems, and it is not exactly multi-platform. On the other hand, for application software one needs a suitable framework that integrates well with the language philosophy and is also practical to work with.

Qt, with its constructs that complement language deficiencies and its truly cross-platform framework that covers practically all necessities, solves both of these problems providing for a very enjoyable and clean development. This is how Qt has come to be the de-facto standard C++ development framework.

Qt is a comprehensive framework for UI and application development. It has a consistent and clean API. It supplies a robust object communication mechanism. It is truly platform independent but still manages to provide all the necessary low-level features and the native UI behaviors of the operating systems it supports. It also provides a set of development tools for rapid application development (RAD) that can replace or integrate with major IDEs such as Visual Studio or Eclipse. It is thoroughly and cleanly documented, has a large and strong community behind it and it is widely adopted. You should use it.

A Comprehensive Library

Qt brings a collection of libraries to provide for all the basic needs in application development. Of course, as is the case with any other framework, Qt does not attend to all the whims of all developers of creation, but it covers practically everything one would expect from a general framework.

Qt has a set a set of libraries called QtCore, covering, as the name implies, a set of core functionalities. Without providing an exclusive enumeration, this core package includes the following:

  • an application environment with event loops and and an event system
  • a GUI system that is one of the strongest assets of Qt
  • templated containers such as lists, queues, vector and maps. These provide a perfect alternative to STL containers, providing mechanisms for Java-style or STL-style iteration and conversion to the STL counterparts
  • resource management classes
  • a model-view system for decoupling data and its views, based on a set of abstract classes
  • a thread framework, which has been around and much appreciated for quite a while
  • string processing and regular expressions
  • OS functionalities such as file processing, printing, networking capabilities and system settings.

Additionally, there are several other packages, most of them being mature and feature-rich in their respective areas. These areas include XML processing, multimedia capabilities, SQL database support, unit testing, OpenGL, WebKit integration, internationalization libraries and tools, SVG manipulation, D-Bus communication, ActiveX controls, and others.

Signals and Slots: a Novel Object Communication Mechanism

Qt provides some language additions through macros that are processed by the Meta-object Compiler (MOC) code generator tool. This results in a more advanced introspection support and the possibility of having certain special mechanisms within Qt-enabled classes. The main such special mechanism is the signals and slots approach of object communication.

Signals and slots provide a better alternative to callbacks, by being loosely coupled and type safe. This means that, when using signals-slots connections, objects don"t know about any objects connected to them, and type safety is guaranteed by limitations on connections based on the compatibility between the arguments of the signals and slots to be connected.

Signals are emitted when an event needs to be announced. Signals are basically bodiless functions.

Slots are regular functions that are called in response to a signal.

The connection is set up through a QObject::connect() call, which, allows only those signals and slots to be connected that have compatible arguments, thus assuring type safety. Objects are unaware of the signals their slots subscribe to or the slots that are connected to their signals, assuring a loose coupling of objects.

Below we provide an example of how a "normal" C++ class can be extended to support signals and slots and how that behavior is realized.

The minimal C++ class declaration:

class Counter
{
public:
 Counter() { m_value = 0; }
 int value() const { 
   return m_value; 
 }
 void setValue(int value);

private:
    int m_value;
};

Augmentation with Qt-specific constructs:

#include 
class Counter : public QObject
{
  Q_OBJECT
public:
  Counter() { m_value = 0; }

The Qt-enabled version has the same internal state but now support signals and slots, having been augmented with the following:

  • The Q_OBJECT macro, which is mandatory for all classes that want to use signals and slots
  • Inheritance from QObject, which is also necessary for signals and slots and for many of the features Qt offers, such as dynamic properties, hierarchical object management, better run-time introspection, etc.
  • The signals macro, which it defines a signal that can be used to notify the outside world of changes, and
  • The public slots macro, which declares a slot that can be used to send signals to.

These special macros are picked up by the MOC code generator tool that generates the appropriate classes and functions that realize all the Qt-specific mechanisms.

Slots need to be implemented by the application programmer. Below is a possible implementation of the Counter::setValue() slot:

Counter::setValue():
void Counter::setValue(int value)
{
    if (value != m_value) {
        m_value = value;
        emit valueChanged(value);
    }
}

You can see the emit statement, which emits a valueChanged() signal with the new value as an argument, whenever a new value is assigned.

Below we show an example of a code snippet, by using two Counter objects, a and b, where the valueChanged() signal of a is connected to the setValue() slot of the object b:

Counter a, b;
QObject::connect(&a, &Counter::valueChanged,
                 &b, &Counter::setValue);
a.setValue(12);     
// a.value() == 12, b.value() == 12

b.setValue(48);     
// a.value() == 12, b.value() == 48

Notice the behavior of the signals and slots mechanism:

  • Calling a.setValue(12) results in a emitting the valueChanged(12) signal, which will trigger the setValue() slot of b, that is call that function
  • This results in b emitting the valueChanged() signal as well, but nothing happens afterwards, since it has not been connected to any other slots

You can see a graphical depiction of how connections are being formed between various objects, as a result of connect() function calls:

Resource Management

Two mechanisms of resource management in Qt are ownership hierarchies and implicit sharing.

An ownership hierarchy consists in an object tree which handles the destruction of descendants. Whenever a new QObject-based object is created on the heap (using new), it can be assigned to have a QObject parent, which will result in a hierarchical object tree in the end. Whenever an object is destroyed from the tree, all its descendants are destroyed as well. In the following code snippet obj2 will be destroyed during the destruction of obj1 (please note that the creation of obj2 does not use a copy-constructor, since copy-constructors are private in QObject).

QObject *obj1 = new QObject();
QObject *obj2 = new QObject(obj1); 
//this also sets obj1 as the parent of obj2

delete obj1;

Implicit sharing refers to an internal mechanism mostly used for Qt containers and large objects. It implements the copy-on-write approach, i.e. it only passes a pointer to the data structure upon every assignment and it copies the full data structure only in case there is a write operation. This makes it sure that no unnecessary copies are created of large data structures, thus improving performance.

In the code below suppose that list1 is a very large list. When list2 is created, there will be no copy made of list1, only a pointer is passed internally, handled by Qt in the background:

// suppose list1 is a very large list of type QList
QList list2 = list1;
QString str1 = list2[100];

When list2[100] is read, it will actually be the exact same memory location list1[100], since no write operation has been performed, and the large list has not been copied.

Truly Cross-platform and Native

Qt is truly cross-platform. This means a single codebase and a single development stream for any number of supported platforms. One only needs to set different build configurations to deploy it to a different system. This was one of the major reasons for choosing it in a large industrial project we have worked on, since the application needed to be supported on Linux-based embedded systems, as well as Windows PCs.

Major operating systems supported are Microsoft Windows, Linux and Mac OS X. Other systems supported include Android, Solaris, HP UX, and others.

The executables generated are truly native. Qt uses native GUI elements and the low-level APIs of the platforms it supports, as shown in the image below. For developers, it provides a platform-independent encapsulation of the local system and its functionalities, such as file processing, networking or printing. Below you can see a simplified image of the Qt architecture involving some of the platforms supported:

A Great API and Good Documentation

The API of Qt is not unlike most of the well-known API frameworks. It is intuitive, robust and convenient. After a slight acquaintance, one can easily find their way around: things are usually named as one would expect them to be named and located where one would expect them to be located.

The API documentation is relevant and simple, not over-encumbered. Sometimes it does take a while to find what one is looking for, and sometimes a bit more information would come in handy. But, overall, it is thorough and extensive with relevant examples, neatly organized and visually appealing, being in line and occasionally even better than the best documentation systems around, such as MSDN or the Java API docs.

Licensing: Open Source and Commercial

Qt provides several types of licenses, the major two being LGPL and Commercial. LGPL can be used for dynamic linking, i.e. simply using the features of Qt through the DLLs. The Commercial license is intended for those who change the Qt libraries themselves and don"t want to make those changes public.

Reliable and Robust

Qt is not a young framework. It"s been around for practically two decades, being widely employed in both personal and industrial settings. It"s been the basis for KDE, the second most popular Linux desktop environment, for more than 15 years, proving stability through millions of users. It"s been used by a wide variety of industrial players and areas, maturing to a level where it can be relied upon to be stable, mature and robust.

Qt also provides bindings for a number of languages beyond C++, such as Python, Java, C#, Ruby, and others. Thus, Qt is so appreciated, that the need arose to use it from other languages as well.

Out-of-the-box Tools

There is a set of tools that is provided along with Qt leveraging the development process.

These include the following:

  • Qt Creator, a simple IDE that supports different build configurations and has strong support for debugging Qt-enabled code,
  • Qt Designer, a graphical GUI editor that can be integrated into other IDEs as well,
  • Qt Linguist, a manager for internationalized texts that can be easily handled within Qt, and
  • Qt Assistant, a Help application that includes the Qt API documentation, but it can also be integrated within own applications to provide a Help system.

There are also some command line tools for compiling Qt projects, generating Qt-specific support classes or compiling UI files.

Visual Studio integration is provided with occasional limitations, but excepting some extra steps that need to be done manually, it is mostly integrated, with debugging being functional as well.

And Much More

Qt has grown to be a platform for both desktop and mobile development, in settings ranging from personal use to industrial or embedded software. As such, it accommodates many needs, all of which cannot possibly be contained within the scope of this article. Nevertheless, for the sake of completeness, we enumerate some further areas where Qt can prove to be an appropriate solution:

  • Declarative UIs: the QtQuick framework and the QML language provide a declarative way of building dynamic user interfaces with fluid transitions and effects, being aimed especially for mobile devices. Logic for the UIs described this way can be written either in JavaScript or in native C++/Qt code.
  • Scripting: QtScript provides a scripting framework, with the possibility to expose parts of the application to the scripting environment, including the signals-slots mechanism.
  • Type introspection and better dynamic casting: through using QObject-based types, run-time type introspection is enabled, as well as a quicker dynamic casting.
  • A mechanism for dynamic properties is provided, where properties can be set and read from objects by a string-based name, without them ever having been declared within their classes. An example is shown below:
QObject obj1;
//adds a new property to obj1
obj1.setProperty("new property", 2.5); 

//d == 2.5
double d = obj1.propety("new property"); 
  • Smart pointers and resource management is also provided, even beyond what C++11 introduced in the meantime.
  • Resource bundling is provided to supply resources, such as image files, in a binary package.

This Is Not a Sales Pitch

No doubt, there are downsides to Qt. When developing exclusively for the Windows platform, a .NET-based solution might come in handier for some. Qt has grown to be quite a large framework, so it might be a little hard to get into. Also, there are occasionally problems with database drivers and threading, and you will stumble upon various problems when using it extensively, as with any framework. But these are exceptions that highlight the otherwise great aspects.

And of course, the advantages outweigh the disadvantages by a mile and you can practically always find your way around problems with the help of some forums and by delving into the API docs.

Sadly, I"ve been away from Qt for quite a few months now. But once I needed to check something about the MVC pattern in general, and I thought that the description of Qt"s models and views might shed some light on matters. And indeed, going back to a quick glimpse on items and views in the Qt API documentation was not only absolutely useful for even non-Qt-related things, but filled me with nostalgic joy and longing after those times when I delved into its wonderfully clean and intriguing world.

So without any other motivation than honest appreciation of the framework, I can heartily recommend it as probably the actual best C++ application framework out there. Asking around people who have worked with it, you will probably find quite similar perspectives to that of the author of this article. For, if you get a chance to explore and use Qt, you"re most definitely in for an adventure that you will dearly look back to long after you"ve trod new ground.

Sponsors

  • comply advantage
  • ntt data
  • 3PillarGlobal
  • Betfair
  • Telenav
  • Accenture
  • Siemens
  • Bosch
  • FlowTraders
  • MHP
  • Connatix
  • UIPatj
  • MetroSystems
  • Globant
  • Colors in projects