Qt containers and C++11 range-based loops


Much has been written on teh interwebs about performance of iterations over Qt containers with Q_FOREACH vs. std iterators vs. Java iterators. However there is very little about how the new C++11 range-based loops work with Qt containers (or maybe I just suck at Googling, but well here I am…). Today I found out that there is a little catch that one has to be very careful about when using range-based loops with Qt containers.

Qt containers are all implicitly shared classes, which means that copying them is very cheap since only shallow copy occurs. When a shared copy is modified (or rather when a non-const method is called) it calls detach() and performs the expensive deep copy. When there are no other copies of the object (i.e. when the reference count is 1) no copying happens when detach() is called. We say that such instance is “not shared”.

To get to the point – the code below performs equally fast in both cases:

QStringList list{ "1", "2", "3", .... };
Q_FOREACH (const QString &v, list) {

for (const QString &v : list) {

However in the following code range-based loop will perform much worse than Q_FOREACH.

class MyClass
   QStringList getList() const { return mList; }
   QStringList mList;


Q_FOREACH (const QString &v, myObject.getList()) {

for (const QString &v : myObject.getList()) {

The difference between the first example and this one is that the QStringList in this example is shared, i.e. reference count of it’s data is higher than 1. In this particular case one reference is held by myObject and one reference is held by the copy returned from the getList() method. That means that calling any non-const method on the list will call detach() and perform a deep copy of the list. And that is exactly what is happening in the range-based loop (but not in the Q_FOREACH loop) and that’s why the range-based loop is way slower than Q_FOREACH in this particular case. The example above could be even simpler, but this way it highlights the important fact that returning a copy from a method means that the copy is shared and has negative side-effects when used with range-based loops. Note that if the method would return a const reference to QStringList, everything would be OK (because const …).

The reason for the speed difference is one peculiarity of Qt containers: they have a const overload of begin() which does not call detach(). Q_FOREACH internally makes a const copy of the list, so the const overload of begin() gets called instead of the non-const one.

On the other hand the range-based loop does not take any copy and simply uses the non-const version of begin(). As we explained above, calling non-const methods on shared Qt containers performs a deep copy. Only exception is when the container itself is const because then the const version of begin() is called and the code will behave the same as Q_FOREACH.

Ironically with stdlib containers (std::vector for example) the situation is exactly the opposite. std iterators are not shared classes so making a copy of an std container always performs a deep copy, but calling a non-const method does not trigger any copying. That means that Q_FOREACH, which always takes a copy of the container would be doing a deep copy in such case while range-based loop, which only calls begin() and end() would not be triggering any copying. Although std containers provide cbegin() and cend() methods to get const interators, there’s no need for the range-based loop to use them, since begin() and end() will always perform equally well on std containers.

To prove my point, here is the benchmark code I used. It’s an extended version of an older benchmark of Qt containers.

#include <QStringList>
#include <QObject>
#include <QMetaType>

#include <qtest.h>
#include <cassert>

enum IterationType


class IterationBenchmark : public QObject

private Q_SLOTS:
    void stringlist_data()

        const int size = 10e6;

        QStringList list;
        for (int i = 0; i < size; ++i) {
            list << QString::number(i);

        QTest::newRow("Foreach") << list << Foreach << false;
        QTest::newRow("Foreach (shared)") << list << Foreach << true;
        QTest::newRow("Range loop") << list << RangeLoop << false;
        QTest::newRow("Range loop (shared)") << list << RangeLoop << true;
        QTest::newRow("Std") << list << Std << false;
        QTest::newRow("Std (shared)") << list << Std << true;
        QTest::newRow("Std Const") << list << StdConst << false;
        QTest::newRow("Std Const (shared)") << list << StdConst << true;

    void stringlist()
        QFETCH(QStringList, list);
        QFETCH(IterationType, iterationType);
        QFETCH(bool, shared);

        if (!shared) {
            // Force detach

        int dummy = 0;

        switch (iterationType) {
        case Foreach:
            QBENCHMARK {
                Q_FOREACH(const QString &v, list) {
                    dummy += v.size();

        case RangeLoop:
            QBENCHMARK {
                for (const QString &v : list) {
                    dummy += v.size();

        case Std:
            QBENCHMARK {
                QStringList::iterator iter = list.begin();
                const QStringList::iterator end = list.end();
                for (; iter != end; ++iter) {
                    dummy += (*iter).size();

        case StdConst:
            QBENCHMARK {
                QStringList::const_iterator = list.cbegin();
                const QStringList::const_iterator = list.cend();
                for (; iter != end; ++iter) {
                    dummy += (*iter).size();



#include "iterationbenchmark.moc"

$ moc iterationbenchmark.cpp > iterationbenchmark.moc
$ g++ iterationbenchmark.cpp `pkg-config --cflags --libs Qt5Core` `pkg-config --cflags --libs Qt5Test` --std=c++11 -fPIC -O3 --o iterationbenchmark
$ ./iterationbenchmark
********* Start testing of IterationBenchmark *********
Config: Using QtTest library 5.4.2, Qt 5.4.2 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 5.1.1 20150422 (Red Hat 5.1.1-1))
PASS   : IterationBenchmark::initTestCase()
PASS   : IterationBenchmark::stringlist(Foreach)
RESULT : IterationBenchmark::stringlist():"Foreach":
     48 msecs per iteration (total: 96, iterations: 2)
PASS   : IterationBenchmark::stringlist(Foreach (shared))
RESULT : IterationBenchmark::stringlist():"Foreach (shared)":
     48 msecs per iteration (total: 96, iterations: 2)
PASS   : IterationBenchmark::stringlist(Range loop)
RESULT : IterationBenchmark::stringlist():"Range loop":
     53.5 msecs per iteration (total: 107, iterations: 2)
PASS   : IterationBenchmark::stringlist(Range loop (shared))
RESULT : IterationBenchmark::stringlist():"Range loop (shared)":
     177 msecs per iteration (total: 177, iterations: 1)
PASS   : IterationBenchmark::stringlist(Std)
RESULT : IterationBenchmark::stringlist():"Std":
     51 msecs per iteration (total: 51, iterations: 1)
PASS   : IterationBenchmark::stringlist(Std (shared))
RESULT : IterationBenchmark::stringlist():"Std (shared)":
     179 msecs per iteration (total: 179, iterations: 1)
PASS   : IterationBenchmark::stringlist(Std Const)
RESULT : IterationBenchmark::stringlist():"Std Const":
     53 msecs per iteration (total: 53, iterations: 1)
PASS   : IterationBenchmark::stringlist(Std Const (shared))
RESULT : IterationBenchmark::stringlist():"Std Const (shared)":
     52 msecs per iteration (total: 52, iterations: 1)
PASS   : IterationBenchmark::cleanupTestCase()
Totals: 10 passed, 0 failed, 0 skipped, 0 blacklisted
********* Finished testing of IterationBenchmark *********

Both Q_FOREACH cases are equally fast because as we explained above, Qt always uses the const iterators and no deep copying happens. Range-based loop with non-shared list performs equally well, because even though it calls detach(), there are no copies to detach from and so no deep copy occurs. However range-based loop with a shared list is over 3 times slower, because detach() here will actually perform a deep copy. The same happens with for loop with non-const std iterators, which is basically just expanded version of range-based loops (range-based loops are just a syntactic sugar for for loops with non-const std iterators). For loops with const std iterators perform equally well as Q_FOREACH, because that is what Q_FOREACH does internally.

To sum this up, when using range-based loops with Qt containers:
Make sure the container is const …

// shared, but const, forces call to QStringList::begin() const,
// which does not call detach()
const QStringList list = objectOfClassA.getList();
for (const QString &v : list) {

… or make sure the container is not shared.

// shared and non-const
QStringList list = objectOfClassA.getList();
// call to non-const method causes detach() and deep copy,
// 'list' is now non-shared
list.append(QLatin1String("some more data"));
// calls non-const begin(), but detach() of non-shared
// containers does not perform deep copy
for (const QString &v : list) {

Note that this just moves the slow deep-copying outside of the loop, but the deep copy still occurs. The point is that you need to be careful not to create a new copy of the ‘list’ after it has been detached on line 5, but before passing it to the loop on line 9. Failing to do so would make the list shared again and the loop would trigger yet another deep copy.

I was very excited when range-based loops were added in C++0x and I’ve been using them in some new C++11 code I wrote since then. But in Qt-based code I’ll be reverting back to the much safer Q_FOREACH. While it is possible to have range-based loops as fast as Q_FOREACH as we’ve shown above, one has to be really careful and constantly think about whether the container is non-shared or at least const and use Q_FOREACH if not. For that reason using Q_FOREACH everywhere is much safer for now.

I know that this is not any ground-breaking revelation and many of you probably even know of it, but I hope that it will still be useful for people who are not aware of the implementation details of Q_FOREACH and range-based loops, or just like me did not realize the importance of difference between shared and non-shared container instance.

16 thoughts on “Qt containers and C++11 range-based loops

  1. Michel Hermier

    I think your example is broken and only expose a common bad practice with shared containers. Since implicit shared assignations are cheap, you should replace MyClass::getList signature to:
    const QStringList &getList() const { return mList; }
    That way you can still copy, while being able to iterate fast since the range for will work with a constant reference. This is almost the same reason why you should pass QString as const QString & for method/function arguments, don’t do an explicit copy until required.

    • Well, yes and no. I could make the method to return const reference, but that would only make sense if I would know ahead that I am only ever going to iterate over it. What if I actually want to modify the list first? Then such signature becomes inconvenient, because I have to explicitly make a non-const copy first.

      Also often you don’t have any choice, if you look at the Qt API there is not a single instance where it returns a const reference instead of value. Regardless of good or bad practice, the point of the example is a to say “be careful when dealing with copies like in this case” because that’s what you do in real code.

      Edit: Qt containers have some methods that return const reference, but that really seems to be an exception

  2. Harald Fernengel

    I assume the fix for the range-for would be:

    for (const QString &v : const_cast(myObject.getList())) …

    looks ugly but should prevent the detach?

    • Unfortunately not. You must explicitly specify the cast type (so it becomes even uglier), and then you can only cast pointers or references and myObject.getList() is neither of those.

      What you could do is something like this:

      struct Wrapper {
      Wrapper(const T &t) : t(t) {}
      const T &get() const { return t; }
      T t;

      for (const QStringList &v : Wrapper<decltype(myObject.getList())>(myObject.getList()).get()) {

      But that is very ugly and requires lots of typing. You could write a macro, but then again, that’s what Q_FOREACH already solves :-)

  3. Sune Vuorela

    Thanks for sharing this.

    I’m frequently telling the same to people I work with. Now we have hard numbers to back it up.

  4. Jan

    And here I thought that the range-baes for loop would be intelligent enough to use the const overload of being/end or cbegin/cend if the iterator value would be const. Maybe the c++ spec should be adjusted for this?

    • const begin()/end() is a Qt speciality, so range-based loop is logically not aware of it (and normal rules for const vs. non-const overload apply here when it calls begin(). The cbegin() alternative makes sense only for shared containers, but only when you actually don’t want to modify the container. Q_FOREACH won’t let you modify the container, so it can safely choose the const overload every time, but for() allows for modifications in the iterated list, and here comes the tricky question: how would it know when to go for const and when not? for (const QString &v : list) (where list = QStringList) is easy, but what about for (qint64 v : list) (where list = QVector)? const or non-const?

  5. Kevin Krammer

    The rule of thumb is:
    range_for on STL containers, never no Qt containers (can cause deep copy)
    foreach on Qt containers, never on STL containers (will cause deep copy)

Comments are closed.