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 –
- std::as_const(container) (C++17)
- qAsConst(container) (Qt 5.7)
- Don't work with rvalues; capture a const-ref in that case
- 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
Linear containers
- QVector std::vector
- QList -
- array-backed 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.