Dec 7, 2017

[CPPCON2017] [Qt] Effective Qt (2017)

Effective Qt

CppCon 2017: Giuseppe D'Angelo “Effective Qt (2017 edition)”

  • Understand the Qt containers
    • Don't use the Qt containers (unless you have to)
    • Qt containers are not actively being developed
    • Datatypes held in Qt containers must be default constructible and copiable
    • No exception safety guarantees
    • Most C++11 APIs still missing
    • All post-C++11 APIs missing
    • No flexibility w.r.t. allocation, comparison, hashing, etc.
    • Use Qt containers if there isn't a STL / Boost equivalent (unlikely)
    • Use Qt containers when interfacing with Qt and Qt-based libraries
    • using the Qt containers, rather than converting back/forth
  • Every time you define a type that you may end up using in a Qt container, remember to declare its typeinfo Q_DECLARE_TYPEINFO.
    • Adding a trait “after the fact” is possible, but it's a potential ABI break
    • Type traits for Qt containers
      • Qt uses type traits to optimize handling of data types in its own containers
      • The most important optimization is:
        • when growing an array of objects, is it OK to use realloc?
          • Safe to do iff the type is relocatable
          • Huge optimization gain over allocating a new buffer; moving elements; deallocating the old buffer
          • Many types are relocatable and could benefit from this optimization
            • E.g. most Qt value classes, thanks to pimpl
    • Relocatability:
      • The compiler cannot tell whether a type is relocatable or not
      • Type authors must annotate relocatable types by using type traits
      • Some libraries let authors add these traits:
        • Qt → Q_DECLARE_TYPEINFO
          • Q_PRIMITIVE_TYPE
          • Q_MOVABLE_TYPE
          • Q_COMPLEX_TYPE
        • EASTL → EASTL_DECLARE_TRIVIAL_RELOCATE
        • STL → *crickets*
      • if a type has pimpl enabled, but, the pimpl has a pointer point back
        to the type instance itself, it's _not_ movable.
        Reason? Since the instance has been moved, the pointer pointing back
        is garbage.
      • A type has pointer pointing to ifself(this type) is not relocatable.
      • Beware, any pointer inside the type that is consider to be relocated should
        consider will it point to a valid address after type instance being relocated.
  • Understand implicit sharing, and be careful about hidden detaches
    • Implicit sharing: a double-edged sword
    • STL is NOT using this, actually, forbidding it.
    • the Qt way of ref counting.
    • COW
    • A Qt value class implementation is typically just a pointer to a pimpl, which contains the reference counter and the actual payload
    • Reference counter is manipulated during an object's lifetime
      • On object creation: refcount is 1
      • Copying an object: refcount is incremented by 1
      • Destroying an object: refcount is decremented by 1; if it reaches zero, deallocate the pimpl
      • Calling a const member function: (nothing)
      • Calling a non-const member function: if the refcount is greater than 1, then detach (= deep copy the the payload)
    • where's the catch?
      • Handing out references to data inside a container does not make the container unshareable
      • It's easy to accidentally detach a container
      • Accidental detaching can hide bugs
      • Code polluted by (out-of-line) detach/destructor calls
      • 小心不要將container內的data記憶體位置傳出。
        直接Update 其 data 不會trigger COW!!! 會造成其他
        Reference到此container instance產生錯誤,以為該位置的data
        仍是舊的data.
        Update data? Through container member function!
    • Accidental detaches - 1
      • “Innocent” code may hide unwanted detaches:
        • QVector<int> calculateSomething();
          const int firstResult = calculateSomething().first();
        • Calls: T& QVector<T>::first();
          • Non-const, may detach and deep copy!
        • Solution is easy: call constFirst()
    • Accidental detaches - 2
      • QMap<int, int> map;
        // ...
        if (map.find(key) == map.cend()) {
        std::cout << "not found" << std::endl;
        } else {
        std::cout << "found" << std::endl;
        }
      • find(key) might detach after the call to cend(), returning an iterator pointing to a “different” end
      • “found” is printed, even if the key isn't in the container
      • Solution: use constFind(key), don't mix iterators and const_iterators
  • Never use Qt's foreach / Q_FOREACH;
    use C++11's range-based for. (Be careful with Qt containers.)
    • this actually applies to boost foreach as well...
    • Disable its usage in your code base by defining QT_NO_FOREACH
    • It will extremely likely be removed in Qt 6
    • If you are not mutating the container, make the container const – 
  • Run clazy on your code base, and fix its warnings
  • Understand Qt string classes. Embrace QStringView.
    • There hasn't been much development around QString / QByteArray in the last few years
    • The only important change that happened is that since Qt 5.9 QStringLiteral / QByteArrayLiteral never allocate memory
    • Ways to create string in Qt:
      • “string”
      • QByteArray(“string”)
        • A sequence of bytes
        • No encoding specified – Akin to std::string
        • Implictly shared
        • Its constructors allocate memory – QByteArray::fromRawData() to avoid (some) allocation
        • QByteArrayLiteral(“string”) never allocates – Since Qt 5.9, this is true on all supported platforms
        • Use it to store byte arrays (i.e. data)
      • QByteArrayLiteral(“string”)
      • QString(“string”)
        • A UTF-16 encoded Unicode string – Support for Unicode-aware manipulations, unlike std::u16string 
        • Implictly shared
        • Its constructors allocate memory – Including QString::fromUtf8(), QString::fromLatin1()
        • Clutch: QString::fromRawData() as non-allocating constructor – Prefer QStringView
        • QStringLiteral(“string”) never allocates – Since Qt 5.9, this is true on all supported platforms – Data is stored UTF-16 encoded in the readonly data segment
        • Use it to store Unicode strings
      • QLatin1String(“string”)
        • A literal type that wraps a const char * and a size – It doesn't manage anything
        • Mostly used in overloads when there's a fast-path implementation possible for Latin-1 strings, and they come from string literals:
        • E.g. substring search:
          int QString::startsWith(const QString &substring);
          int QString::startsWith(QLatin1String substring);
          QString str = "...";
          if (str.startsWith(QString("foo"))) // allocates a temp. QString
             doSomething();
          if (str.startsWith(QLatin1String("foo"))) // does not allocate + uses
          doSomething();  // optimized implementation
      • QStringLiteral(“string”)
      • QString::fromLatin1(“string”)
      • QString::fromUtf8(“string”) 
      • tr(“string”)
      • QStringView(u”string”)
        • New in Qt 5.10
        • as an interface type
        • The primary use case for QStringView is for functions parameters
          • If a function needs a Unicode string, and it doesn't store it, use QStringView
        • Unicode safe
        • Never allocates
        • Can be built from a wide variety of sources
        • as an alloc-free tokenizer
          • To extract substrings, without allocating memory
          • QString str = "...";
            QRegularExpression re("reg(.*)ex");
            QRegularExpressionMatch match = re.match(str);
            if (match.hasMatch()) {
              QStringView cap = match.capturedView(1); // no allocations
            // ...
            }
        • A non-owning view over a UTF-16 encoded string:
          • QString
          • QStringView
          • std::u16string
          • Array and std::basic_string of QChar, ushort, char16_t, wchar_t (on Windows)
        • Literal type; akin to C++17's std::u16string_view
        • Offers the majority of the const QString APIs, without the need of constructing a QString first
          • More APIs, QStringBuilder support etc. expected in 5.11
  • Prefer the Standard Library ones.
  • Qt containers was there for reasons:
    • Qt needed to work on platforms without a STL
    • Qt didn't want to expose Standard Library symbols from its ABI
    • Qt containers used in Qt APIs, and available for applications
  • Qt containers use camelCase
    • STL use snake_case

Linear containers

  • QVector std::vector
  • QList -
    • array-backed list
      • Not a linked list
    • Terribly inefficient if the the object stored are bigger than a pointer
      •  Allocates every individual object on the heap
    • Avoid using it (unless you have to)
    • use QVector instead
    • might simply become a typedef for QVector, and a new type (QArrayList?) introduced in Qt6
  • QLinkedList std::list
  • - std::forward_list
  • QVarLengthArray -
    • preallocates space for a given number of objects
    • avoid hitting the heap
    • a vector with SSO
    • Similar: Boost's small_vector
  • - std::deque
  • - std::array

associative containers

  • QMap std::map
  • QMultiMap std::multimap
  • QHash std::unordered_map
  • QMultiHash std::unordered_multimap
  • - std::set
  • - std::multiset
  • QSet std::unordered_set
  • - std::unordered_multiset

Relocable means:

  • Relocability is independent from being POD
  • Relocatable types may have non-trivial constructors/destructors
    • E.g. Qt pimpl'd value classes
  • A trivial type may not be relocatable
    • E.g. if the address of an object is its identity
    • All C data types are trivial, but non necessarily relocatable

Dont' use deprecated Qt APIs

  • Always define QT_DEPRECATED_WARNINGS
    • Makes the compiler emit warnings if using deprecated APIs
  • Define QT_DISABLE_DEPRECATED_BEFORE to the version of Qt you develop
    against
    • Turns usage of deprecated APIs into hard errors, iff they have been deprecated in that Qt version or in a earlier one
    • No “new” errors if you upgrade Qt
      • E.g. in qmake:
        DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x050900

Never use QList for your own code

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.