Using C++11 Initializer Lists in Qt Ways

In this post, I want to show some examples how C++11’s initializer lists can improve some typical Qt code. I’ll show how C++11 makes the initialisation of container-type variables very simple and how it makes static const data members possible at all.

Example 1: Initialising Container-Type Variables

Initialising container-type variables in C++03 and older was cumbersome. The typical re-implementation of the function QAbstractItemModel::roleNames() proves this point. Subclasses of QAbstractItemModel must reimplement this function such that, say, a QML ListView can use a C++ role as a QML property. The old way looked similar to this.

// Code Snippet #1 (Old C++)
QHash<int32, QByteArray> MyModel::roleNames() const {
    static QHash<int32, QByteArray> roles;
    if (roles.isEmpty()) {
        roles[ROLE_LAST_NAME] = "lastName";
        roles[ROLE_FIRST_NAME] = "firstName";
    }
    return roles;
}

The first time roleNames() is called, the static variable roles is empty and it is initialised with the two name-value pairs. The initialisation is skipped in further calls.

The way C++11 way of writing this function looks as follows.

// Code Snippet #2 (C++11)
QHash<int32, QByteArray> MyModel::roleNames() const {
    return QHash<int32, QByteArray>{
        { ROLE_LAST_NAME, "lastName" },
        { ROLE_FIRST_NAME, "firstName" }
    };
}

As Simon pointed out in the comments, this version is less efficient than the original version. The hash map with the role names is created every time the function roleNames is called. This is easy to fix. We can declare a static const variable roles as in the original example, initialise it with the hash map, and return roles.

// Code Snippet #2b (C++11)
QHash<int32, QByteArray> MyModel::roleNames() const {
    static const auto roles = QHash<int32, QByteArray>{
        { ROLE_LAST_NAME, "lastName" },
        { ROLE_FIRST_NAME, "firstName" }
    };
    return roles;
}

Now, the static variable roles is created only once for all instances of MyModel. Thanks to C++11’s auto and initializer list, the function roleNames() also becomes simpler to read.

The solution for MyModel::roleNames() above can be used in general to initialise container-type variables. The classic example would be initialising a data member of type QStringList. Before C++11, we would have done it as follows.

// Code Snippet #3 (Old C++)
// File: decoder.h
class Decoder {
private:
    QStringList m_combinedChars;
}

// File: decoder.cpp
Decoder::Decoder() {
    m_combinedChars.append(", "); // Or: push_back() instead of append()
    m_combinedChars.append("? ");
    m_combinedChars.append("! ");
    // ...
}

In C++11, the constructor becomes much simpler.

// Code Snippet #4 (C++11)
// File: decoder.cpp
Decoder::Decoder() :
    m_combinedChars{ ", ", "? ", "! " }
{
    // ...
}

Example 2: Static Constant Data Members of Container Types

We are going to do something now that pretty cumbersome in the pre-C++11 days (see Patrick’s comment below how to do it in the olden days). We’ll declare a container-type variable constant and initialise it in the constructor.

// Code Snippet #5 (C++11)
// File: decoder.h
class Decoder {
private:
    const QStringList m_combinedChars;
}

// File: decoder.cpp
Decoder::Decoder() :
    m_combinedChars{ ", ", "? ", "! ", "; ", ": ", " (", ") " } 
{
    // ...
}

As m_combinedChars is the same constant for all instances of Decoder, we could declare it as static as well.

// Code Snippet #6 (C++11)
// File: decoder.h
class Decoder {
private:
    static const QStringList m_combinedChars;
}

// File: decoder.cpp
const QStringList Decoder::m_combinedChars = 
    QStringList{ ", ", "? ", "! ", "; ", ": ", " (", ") " }

Decoder::Decoder() { /* ... */ }

This is a lot better than with C++03 and before, where we were only able to declare data members of built-in types as static const.

My first thought with the above code was that I should be able to simplify it further using C++11’s auto.

// Code Snippet #7 (C++11)
// File: decoder.cpp
const auto Decoder::m_combinedChars = 
    QStringList{ ", ", "? ", "! ", "; ", ": ", " (", ") " }
// Compiler error: conflicting declaration 'const auto Decoder::m_combinedChars'

The above code is perfectly fine, as we took care that the declaration in the header file and the initialisation in the source file yield the same type for m_combinedChars. However, it is pretty easy to screw this up.

// Code Snippet #8 (C++11)
// File: decoder.h
class Decoder {
private:
    static const double hugeNumber;  // Type is double!
}

// File: decoder.cpp 
const auto Decoder::hugeNumber = 5.3f; // Type is float!

We have a real type conflict in this example. hugeNumber has type double in the header file and type float in the source file. With auto, the compiler never tries implicit conversions. Why should the compiler make an exception here.

The real problem is the code duplication. The compiler forces us to declare static data members in the header and initialise them in the source file. We should be allowed to initialise the data member in the header file and get rid of the initialisation in the source file. And indeed, C++11 allows this.

// Code Snippet #9 (C++11)
// File: decoder.h
class Decoder {
private:
    static constexpr auto hugeNumber1 = 5.3;    // Type is double!
    static const double hugeNumber2 = 5.3;      // Compiler error!
}

We must use constexpr instead of const. Initialising a static const data member in the class declaration is still only allowed for integral and enumeration types. g++ does not fail on the second declaration above in older version, but this has always been non-standard.

Ideally, we would like to declare and initialise m_combinedChars in the same way as hugeNumber1.

// Code Snippet #10 (C++11)
// File: decoder.h
class Decoder {
private:
    static constexpr auto hugeNumber1 = 
        QStringList{ ", ", "? ", "! ", "; ", ": ", " (", ") " };
    // Compiler error: field initializer is not constant
}

Finally, we are out of luck. This is not possible in C++11! The compiler cannot figure out that the initialisation expression is constant. The constructors of the class QStringList are too complex.

The above code works fine for trivial classes whose constructors could be generated by the compiler. The class QPoint, for example, is such a trivial class.

// Code Snippet #11 (C++11)
// File: decoder.h
class Decoder {
private:
    static constexpr auto point = QPoint{3, 7}; 
}

// File: qpoint.h
// Kind of constructors considered trivial!
inline QPoint::QPoint() : xp(0), yp(0) {}
inline QPoint::QPoint(int xpos, int ypos) : xp(xpos), yp(ypos) {}

5 thoughts on “Using C++11 Initializer Lists in Qt Ways

  1. Nice article!

    > This is a lot better than with C++03 and before, where
    > we were only able to declare data members of built-in
    > types as static const.

    Actually it was possible to have a static, constant container type variable as a class member using a helper function to do the initialization. For example…

    In the class declaration:

    static const std::vector _MimeMpeg;
    

    In the implementation:

    static std::vector MakeMimeAAC()
    {
    	std::vector v;
    	v.push_back("audio/aacp");
    	v.push_back("audio/aac");
    	return v;
    }
    
    const std::vector FeedFormat::_MimeAAC = MakeMimeAAC();
    

    Not that bad compared to the new C++11 way which is even better of course.

    • Thanks for pointing this out! So, it’s possible to use static const variables in old C++, but it’s pretty cumbersome compared to C++11.

  2. It is most unfortunate how the role names example is worse with initializer syntax. Every time the function is called a new QHash is allocated and populated (including hashing, etc.). The initializer list syntax is merely syntactic sugar, it does not avoid the cost of hashing and allocating memory. The version with the function static variable is superior in terms of performance (hash only once populated) and Memory consumption (implicitly shared copy).

    • Good point! Then, let’s combine the initializer list with the static variable to enjoy both worlds: simple syntax and best performance.

      QHash<int32, QByteArray>  MyModel::roleNames() const {
          static const auto roles = QHash<int32, QByteArray>{
              { ROLE_LAST_NAME, "lastName" },
              { ROLE_FIRST_NAME, "firstName" }
          };
          return roles;
      }
      

Comments are closed.