QTL or STL?

First, a little history: when I started working with Qt I was coming from a STL/Boost background and was accustomed to using the STL containers and strings. On the first Qt project that I’ve worked on, a Linux port for a large-scale C++ system, the team’s approach was to use Qt strictly for the UI and the STL, Boost and other libraries for everything else.

At that time, I didn’t even know there was a QTL and I was using std::(w)string pretty much everywhere. After working on more Qt projects, I now use the QTL everywhere and have extensive experience with both libraries. My conclusion is that there is no clear winner in this comparison, that the choice can be quite complicated depending on the project type, and switching from one library to the other poses some subtle difficulties.

Containers

The STL and Qt have a different philosophy when it comes to containers:

STL

  • The containers are very parameterizable and built for maximum speed.
  • Has performance guarantees for each container.
  • Each implementation can use copy-on-write as it sees fit.
  • Free, generic functions are prefered to member functions.
  • There are multiple container implementations, just as there are multiple standard library implementations.
  • … and those implementations are not binary compatible, but they must obey the C++ standard. This includes the interfaces and performance guarantees.
  • Each implementation is free to do whatever optimizations that it pleases as long as it respects the standard.
  • Provides regular iterators.
  • Only includes a hash in TR1.
  • Only includes a variable length array in TR1.
  • Have a header-only implementation and the compiler has access to all the source code.
  • Has the <algorithm> header.

QTL

  • Containers are not parameterizable. Don’t like the default memory allocator? Well, that’s too bad…
  • Provides a list of performance guarantees in the docs.
  • Use copy-on-write to avoid needless expensive copies.
  • Lots of member functions provided for convenience. e.g: contains, indexOf, removeAt, takeAt, uniqueKeys, startsWith, etc
  • One implementation that is binary compatible between major releases but not major versions. i.e: Qt 4.5 is compatible up to Qt 4.9, but Qt 3.2.2 is not compatible with Qt 4.0
  • Qt containers do optimizations which are available on all operating systems.
  • Can use STL-style iterators or Java-style iterators.
  • Includes a hash and multihash implementation.
  • Includes QVarLengthArray.
  • Are not header only. Some functions are not available when building your program and they can’t be used for global optimizations.
  • Has the <QtAlgorithm> header.

If you will use the Qt containers together with the Qt framework, you will get a few other advantages:

  • Qt containers are streamable by default with QDataStream. This means that you only have to write a streaming operator for your user defined contained type.
  • They are used extensively in Qt’s API. If you use STL containers you will almost certainly have to make conversions.
  • Some signal slots connections copy their parameters. Since the Qt containers use COW, copying them is less costly.
  • You can use Q_FOREACH with Qt containers. Since Q_FOREACH does a copy of the target container, it would be too expensive to use with the STL.

My opinion is that the biggest advantage of the QTL is that it has the same implementation (including binary compatibility) on all OSes supported by Qt.  Some STL implementations might be below par when it comes to performance or they might be missing functionality. Some platforms don’t even have an STL! On the other hand, the STL is more customizable and is available in its entirety in header files… Like I said, there is no clear winner.

Strings

When deciding about using QString instead of std::(w)string, there are few points to consider:

  • QString scores a lot higher than std::basic_string when it comes to usability.
  • It offers Unicode support.
  • It lives in a dll/so, which means that whole program optimizations won’t be available. This can be worked around by linking with Qt statically, but the static vs dynamic debate is quite complicated and a decision has wider reaching effects.
  • Doesn’t allow customization through character traits or custom allocators. In fact, QString is a regular class, not a template and it can easily be forward declared.

Personally, I almost always use QString if I’m using Qt. I originally used conversion helpers to avoid a deep Qt dependency, but unless most of your program is Qt-free, it’s not worth it to use both QString and std::string.

Not a drop in replacement

Qt containers are STL-compatibile in the way that they provide iterators that match the STL algorithm library and the container interfaces mimic the STL ones. In addition to this, there’s an STL compatibility mode that allows you to directly convert to and from a STL container when you’re working with its Qt counterpart. e.g: QVector::toStdVector

Even so, you can’t swap between Qt and STL containers as easily as replacing a type definition:

  • QVector::at doesn’t do range checking besides an assert.
  • QList provides index-based access and is more similar to a std::deque. QLinkedList is probably the closes thing to std::list.
  • There are no constBegin, constEnd, constFind, etc functions in the STL. You might not easily be able to take advantage of Qt’s COW implementation.
  • The QMap and QMultiMap interfaces differ from the std::map and std::multimap ones. In particular, there’s no equal_range function.
  • Qt containers can’t be parametrized with an allocator.

Smart pointers

Thiago Maceira from Nokia/ex-Trolltech has done an excellent summary of the Qt smart pointers. If you read this summary, you’ll know 99% of what’s to know about Qt’s smart pointers.

I have added only a few observations from experience:

  • Unfortunately TR1 is not as wide spread as I would like at this point and I find it much easier to use the Qt shared pointer instead of introducing a large Boost dependency in a project.
  • QScopedPointer supports d-pointers and custom deleters. AFAIK, neither scoped_ptr nor auto_ptr do.

9 thoughts on “QTL or STL?

  1. “No variable length array.”

    Just curious: what about std::vector and std::valarray makes them insufficient for use as a ‘variable length array’ classes?

    • Hi! That item is not precisely worded and I’m going to change it, but here’s why I think that std::vector or std::valarray won’t satisfy the same use cases as QVarLengthArray:

      The most interesting thing about QVarLengthArray is that it can use both stack and heap storage. So if you say

      const int size = foo() + 2;
      QVarLengthArray<int,512> array(size);

      if size is < 512 all the storage is allocated on the stack, else it is allocated on the heap. This behaviour could be taken advantage of in function calls where memory allocation would be too expensive for instance.

      On the other hand:

      - std::vector always allocates storage on the heap
      - While it is true that std::valarray is optimized for mathematical operations, it still uses the heap. (at least in the MSVC 2008 STL which I've checked)

      Another candidate for STL variable length array is std::tr1::array, with the observation that a TR1 array has a fixed size and QVarLengthArray is resizeable.

  2. About QString you write;
    > It lives in a dll/so, which means that whole program
    > optimizations won’t be available.

    Could you explain what that means or why its a bad thing?

  3. You write;
    > Containers are not parameterizable. Don’t like the default
    > memory allocator? Well, that’s too bad…

    Thats an interesting view on the topic :) Most people I spoke to are very happy that someone else puts all their time into fine tuning things like the memory allocator so you don’t have to.
    Maybe you are assuming that nobody is putting effort into optimisations like that into Qt? AFAIK that assumption would be false.

  4. Hi Thomas.
    If Qt is linked dynamically in a project(as a DLL or so), it means that some of the container and QString code can’t be seen by the compiler or linker when we’re building that project.

    Both GCC and MSVC offer a late (link-time) whole program optimization that can do cross-module inlining and other such optimizations. In MSVC it’s called LTCG and in GCC it’s called LTO.

    Since the QString code is already linked in the QtCore binary, it will not be taken in consideration for this optimization. I don’t know the exact impact this will have on performance, but I remember that Bjorn Erik Nielsen mentioned in his Qt DevDays presentation that turning off LTCG when building Qt resulted in a 10-15% performance loss on average.

  5. Re: customizable allocator

    I agree that most people will never write their own allocator since the default one will be sufficient for them.

    However, there are special cases when using a custom allocator would significantly improve performance. e.g: Using a pool allocator when working with lots of small objects.

    I assume that Trolltech didn’t want to incur the costs of allocator customizations. So far this has not bothered me, and I would just use a STL container for that special case.

  6. Regarding QString, it has another very important advantage: It is character-set-aware and even can transliterate between any supported character-set out of the box. That’s something impossible with std::wstring, let alone std::string, but needed for most applications that handle external data in any way.