Porting to Qt 6 - Qt Core
Qt 6 is a result of the conscious effort to make the framework more efficient and easy to use.
We try to maintain binary and source compatibility for all the public APIs in each release. But some changes were inevitable in an effort to make Qt a better framework.
In this topic we summarize those changes in Qt Core, and provide guidance to handle them.
Container Classes
QHash, QMultiHash, QSet
qHash() Signature
For custom types, QHash and QMultiHash rely on you providing a custom qHash() function in the same namespace. In Qt 4 and Qt 5, the return value and optional second argument of a qHash
function was of type uint
. In Qt 6, it is size_t
.
That is, you need to change
uint qHash(MyType x, uint seed);
to
size_t qHash(MyType x, size_t seed);
This allows QHash, QMultiHash and QSet to hold more than 2^32 items on 64 bit platforms.
Stability of References
The implementation of QHash and QMultiHash in Qt 6 got changed from a node based approach to a two stage lookup table. This design allows to keep the memory overhead of a hash instance very small, while at the same time giving good performance.
One behavioral change to note is that the new QHash implementation will not provide stable references to elements in the hash when the table needs to grow, or when entries are removed. Applications that rely on such stability might now run into undefined behavior.
Removal of QHash::insertMulti
In Qt 5, QHash could be used to create multi-valued hashes by using QHash::insertMulti, and QMultiHash was deriving vom QHash.
In Qt 6, both types and use cases are distinct, and QHash::insertMulti got removed.
QVector, QList
Prior to Qt 6, QVector and QList were separate classes. In Qt 6, they are unified: Qt 5 QList implementation is gone and both classes use updated QVector implementation instead. QList is the class with the actual implementation and QVector is an alias (typedef) to QList.
QList's fromVector() and toVector(), and QVector's fromList() and toList(), no longer involve data copying in Qt 6. They now return the object that they were called for.
API Changes
QList's (and hence QVector's) size type is changed from int
to qsizetype
. Together with the size type, all relevant methods' signatures are updated to use qsizetype
. This allows QList to hold more than 2^31 items on 64 bit platforms.
When upgrading the code base to Qt 6, this API change would most likely result in compiler warnings about narrowing type conversions. Having the following example code:
void myFunction(QList<MyType> &data) { int size = data.size(); // ... const int pos = getInsertPosition(size); data.insert(pos, MyType()); // ... }
you would need to update it to use either qsizetype
or an auto keyword:
void myFunction(QList<MyType> &data) { auto size = data.size(); // ... const auto pos = getInsertPosition(size); data.insert(pos, MyType()); // ... }
Alternatively, you may use type casting and cast everything to int
or to qsizetype
.
Note: If you want to build against both Qt 5 and Qt 6, the auto keyword is a good solution to cover signature differences between the versions.
Memory Layout
QList received multiple changes related to the memory layout in Qt 6.
In Qt 5, sizeof(QList<T>)
was equal to a size of a pointer. Now, the extra pointer indirection is removed and QList data members are directly stored in the object. By default, expect sizeof(QList<T>)
to be equal to the size of 3 pointers.
At the same time, memory layout of the elements is also updated. QList now always stores its elements directly in the allocated memory region as opposed to Qt 5, where certain objects were separately allocated on the heap and pointers to the objects were placed into the QList instead.
Note that the latter, in particular, affects large objects. To have Qt 5 behavior, you could wrap your objects into smart pointers and store these smart pointers in QList directly. In this case, the type of your QList would be QList<MySmartPointer<MyLargeObject>>
as opposed to QList<MyLargeObject>
in Qt 5.
Stability of References
There are several changes made to the QVector/QList implementation. The QVector related ones are: insertion at the beginning is optimized (similarly to QList in Qt 5) and element removal can reallocate in order to remove the unused capacity. The QList related one is: memory layout for the elements is simplified.
Important: These changes impact the stability of references. In Qt 6, you should consider any size or capacity modifying method to invalidate all references, even when QList is not implicitly shared. Exceptions to this rule are documented explicitly.
Applications that rely on certain reference stability might run into undefined behavior when upgraded to use Qt 6. You should pay extra attention to cases where QVector or QList with a non C-compatible array layout were used originally.
QtConcurrent and Related Classes
QFuture and QFutureWatcher
In Qt 6, there are no changes that introduce source compatibility breaks in code that was using QFuture and QFutureWatcher classes. However there were some improvements which caused the following behavioral changes:
- After pausing QFuture or QFutureWatcher (by calling
pause()
orsetPaused(true)
), QFutureWatcher will not immediately stop delivering progress and result ready signals. At the moment of pausing there may be still computations that are in progress and cannot be stopped. Signals for such computations may be still delivered after pause, instead of being postponed and reported only after next resume. To get notified when pause actually took effect, QFutureWatcher::suspended() signal can be used. In addition, there are newisSuspending()
andisSuspended()
methods, to check if the QFuture is in the process of suspending or it's already in the suspended state. Note that for consistency reasons, for both QFuture and QFutureWatcher the pause-related APIs were deprecated and replaced by similar methods having "suspend" in the name instead. - QFuture::waitForFinished() will now wait until QFuture is actually in the finished state, instead of exiting as soon as it is not in the running state. This prevents
waitForFinished()
from exiting immediately, if at the moment of calling it the future is not started yet. The same applies to QFutureWatcher::waitForFinished(). This change won't affect the behavior of code that was using QFuture with QtConcurrent. Only the code that was using it with the undocumentedQFutureInterface
may be affected.
QPromise
In Qt 6, the new QPromise class should be used instead of unofficial QFutureInterface as a "setter" counterpart of QFuture.
QtConcurrent::run()
QtConcurrent::run() has been improved to work with a variable number of arguments, so the signatures are changed to:
// run template <typename T> QFuture<T> run(Function &&f, Args &&...args) // run with a QThreadPool argument template <typename T> QFuture<T> run(QThreadPool *pool, Function &&f, Args &&...args)
As a side effect, if f
is a pointer to a member function, the first argument of args
should be the object for which that member is defined (or a reference, or a pointer to it). So instead of writing:
QImage image = ...; QFuture<void> future = QtConcurrent::run(&image, &QImage::invertPixels, QImage::InvertRgba);
You have to write:
QFuture<void> future = QtConcurrent::run(&QImage::invertPixels, &image, QImage::InvertRgba);
Other methods of QtConcurrent have no behavioral changes and do not introduce source compatibility breaks.
String related classes
QStringView
Starting with Qt6 it is generally recommended to use QStringView over QStringRef
. QStringView references a contiguous portion of a UTF-16 string it does not own. It acts as an interface type to all kinds of UTF-16 strings, without the need to construct a QString first. The QStringView class exposes almost all read-only methods of QString and the previously existing QStringRef
class.
Note: Care must be taken to ensure that the referenced string data (for example, owned by a QString) outlives the QStringView on all code paths.
Note: If a QStringView wraps a QString, care needs to be taken since unlike QStringRef
QStringView will not update the internal data pointer once the QString data relocates.
QString string = ...; QStringView view{string}; // Appending something very long might cause a relocation and will // ultimately result in a garbled QStringView. string += ...;
QStringRef
In Qt6 QStringRef got removed from Qt Core. To ease porting of existing applications without touching the whole code-base, the QStringRef
class did not vanish completely and instead it got moved into the Qt5Compat module.
If you want to use QStringRef
further, you need to link against the new Qt5Compat module and add this line to your qmake .pro
file:
QT += core5compat
In case you already ported your application or library to the cmake build system, add the following to your CMakeList.txt
:
PUBLIC_LIBRARIES Qt::Core5Compat
Unfortunately, some methods exposed by QString returning a QStringRef
, could not be moved to Qt5Compat. Therefore some manually porting may be needed. If your code uses one or more of the following functions you need to port them to use QStringView or QStringTokenizer.
Change code using QStringRef
:
QString string = ...; QStringRef left = string.leftRef(n); QStringRef mid = string.midRef(n); QStringRef right = string.rightRef(n); QString value = ...; const QVector<QStringRef> refs = string.splitRef(' '); if (refs.contains(value)) return true;
to:
QString string = ...; QStringView left = QStringView{string}.leftRef(n); QStringView mid = QStringView{string}.midRef(n); QStringView right = QStringView{string}.rightRef(n); QString value = ...; const QList<QStringView> refs = QStringView{string}.split(u' '); if (refs.contains(QStringView{value})) return true; // or const auto refs = QStringView{string}.tokenize(u' '); for (auto ref : refs) { if (ref == value) return true; }