Warning
This section contains snippets that were automatically translated from C++ to Python and may contain errors.
Changes to Qt Core#
Changes to containers, strings, serialization and I/O classes.
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
, QMultiHash
and QSet
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 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 one is: insertion at the beginning is optimized (similarly to QList
in Qt 5). 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.
View classes in Qt6#
General Overview#
There are several new View
classes coming with Qt6. There is the already existing QStringView
, now accompanied by QByteArrayView
and followed by a specialized QUtf8StringView
and a more universal QAnyStringView
.
Introduction to view classes on the example of QStringView#
The QStringView
class provides a unified view on UTF-16 strings with a read-only subset of the QString
API. Unlike QString
, which keeps its own copy of the string (possibly ref-counted), QStringView
provides a view of a string that is stored elsewhere.
char hello[]{ "Hello." }; // narrow multi-byte string literal QString str{hello}; // needs to make a copy of the string literal QString strToStr(str); // atomic increment involved to not create a copy of hello again // The above code can be re-written to avoid copying and atomic increment. QStringView view{ u"Hello." }; // view to UTF-16 encoded string literal QStringView viewToView{ view }; // view of the same UTF-16 encoded string literal
The string "Hello."
is stored in the binary and is not allocated at run-time. view
is only a view onto the string "Hello."
, therefore no copy has to be created. When we copy a QStringView
, the viewToView
observes the same string as the copied-from view
is observing. This means that viewToView
does not need to create a copy or an atomic increment. They are views onto the existing string "Hello."
.
Views as function argument#
Views should be passed by value, not by reference-to-const.
void myfun1(QStringView sv); // preferred void myfun2(const QStringView &sv); // compiles and works, but slower
View manipulation functions#
QStringView
supports functions that let us manipulate the view of the string. This allows us to change the view without creating a partial copy of the viewed string.
QString pineapple = "Pineapple"; QString pine = pineapple.left(4); // The above code can be re-written to avoid creating a partial copy. QStringView pineappleView{ pineapple }; QStringView pineView = pineappleView.left(4);
Non null-terminated strings and strings containing `` ‘0’``
QStringView
supports both null-terminated and non null-terminated strings. The difference comes from the way you initialize the QStringView
:
QChar aToE[]{ 'a', 'b', 'c', 'd', 'e' }; QStringView nonNull{ aToE, std::size(aToE) }; // with length given QStringView nonNull{ aToE }; // automatically determines the length QChar fToJ[]{ 'f', 'g', 'h', '\0', 'j' }; // uses given length, doesn't search for '\0', so '\0' at position 3 // is considered to be a part of the string similarly to 'h' and 'j QStringView nonNull{ fToJ, std::size(fToJ) }; QStringView part{ fToJ }; //stops on the first encounter of '\0'
Ownership model of views#
As views
do not own the memory they reference, care must be taken to ensure that the referenced data (for example, owned by a QString
) outlives the view
on all code paths.
QStringView sayHello() { QString hello("Hello."); return QStringView{ hello }; // hello gets out of scope and destroyed } void main() { QStringView hello{ sayHello() }; qDebug() << hello; // undefined behavior }
Converting an QStringView to QString#
QStringView
will not implicitly or explicitly convert to a QString
, but can create a deep copy of its data:
void print(const QString &s) { qDebug() << s; } void main() { QStringView string{ u"string"}; // print(string); // invalid, no implicit conversion // QString str{ string }; // invalid, no explicit conversion print(string.toString()); QString str = string.toString(); // create QString from view }
Important notes#
By leveraging the new view classes, one can achieve a lot of performance boost in many use cases. However, it is important to know that there might be some caveats. Therefore it is important to remember:
Views should be passed by value, not by reference-to-const.
Constructing a view with a negative length is undefined behavior.
Care must be taken to ensure that the referenced data (for example, owned by a
QString
) outlives the view on all code paths.
The QStringView class#
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 += ...;
The QStringRef class#
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, see Using the Qt5Compat module .
Unfortunately, some methods exposed by QString
returning a QStringRef
, could not be moved to Qt5Compat. Therefore some manual 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
. It is also recommended to use tokenize
over split
for performance critical code.
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}.left(n); QStringView mid = QStringView{string}.mid(n); QStringView right = QStringView{string}.right(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; }
The QFuture class#
To avoid unintended usage of QFuture
, there were some changes to QFuture
API in Qt 6, which may introduce source compatibility breaks.
Implicit conversions between QFuture and other types#
Conversion of QFuture<T>
to T
has been disabled. The casting operator was calling result()
, which may lead to undefined behavior if the user has moved the results from QFuture
via takeResult()
before trying to do the conversion. Use result()
or takeResult()
methods explicitly, where you need to convert QFuture<T>
to T
.
The implicit conversion from QFuture<T>
to QFuture<void>
has been also disabled. If you really intend to do the conversion, use the explicit QFuture<void>(const QFuture<T> &)
constructor:
QFuture<int> future = ... QFuture<void> voidFuture = QFuture<void>(future);
Equality operators#
The equality operators of QFuture
have been removed. They were comparing the underlying d-pointers instead of comparing the results, which is not what users might expect. If you need to compare QFuture
objects, use QFuture::result()
or QFuture::takeResult()
methods. For example:
QFuture<int> future1 = ...; QFuture<int> future2 = ...; if (future1.result() == future2.result()) // ...
Behavioral Changes to QFuture and QFutureWatcher#
In Qt 6, there were some improvements to QFuture
and QFutureWatcher
which caused the following behavioral changes:
After pausing
QFuture
orQFutureWatcher
(by callingpause()
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,suspended()
signal can be used. In addition, there are newisSuspending()
andisSuspended()
methods, to check if theQFuture
is in the process of suspending or it’s already in the suspended state. Note that for consistency reasons, for bothQFuture
andQFutureWatcher
the pause-related APIs were deprecated and replaced by similar methods having “suspend” in the name instead.
waitForFinished()
will now wait untilQFuture
is actually in the finished state, instead of exiting as soon as it is not in the running state. This preventswaitForFinished()
from exiting immediately, if at the moment of calling it the future is not started yet. The same applies towaitForFinished()
. This change won’t affect the behavior of code that was usingQFuture
with QtConcurrent. Only the code that was using it with the undocumentedQFutureInterface
may be affected.
isFinished()
now reflects the finished-state of theQFuture
rather than returning false untilfinished()
has been emitted.
The QPromise class#
In Qt 6, the new QPromise
class should be used instead of unofficial QFutureInterface as a “setter” counterpart of QFuture
.
IO Classes#
The QProcess class#
In Qt 6, the start()
overload that interprets a single command string by splitting it into program name and arguments is renamed to startCommand()
. However, a start()
overload that takes a single string, as well as a QStringList
for arguments exists. Since the QStringList
parameter defaults to the empty list, existing code only passing a string will still compile, but will fail to execute the process if it is a complete command string that includes arguments.
Qt 5.15 introduced deprecation warnings for the respective overload to make it easy to discover and update existing code:
QProcess process; // compiles with warnings in 5.15, compiles but fails with Qt 6 process.start("dir \"My Documents\""); // works with both Qt 5 and Qt 6; also see QProcess::splitCommand() process.start("dir", QStringList({"My Documents"}); // works with Qt 6 process.startCommand("dir \"My Documents\"");
QProcess::pid() and the Q_PID type have been removed; use processId()
instead to get the native process identifier. Code using native Win32 APIs to access the data in the Q_PID as a Win32 PROCESS_INFORMATION
struct is no longer supported.
Meta-Type system#
The QVariant class#
QVariant
has been rewritten to use QMetaType
for all of its operations. This implies behavior changes in a few methods:
QVariant::isNull()
now only returnstrue
if theQVariant
is empty or contains anullptr
. In Qt 5, it also returned true for classes in qtbase which had anisNull
method themselves if that one returned true. Code relying on the old behavior needs to check whether the contained value returns isNull – however such code is unlikely to occur in practice, asisNull()
is rarely the property one is interested in (compareQString::isEmpty()
/isNull()
andQTime::isValid
/isNull
).
QVariant::operator==
usesQMetaType::equals
in Qt 6. Therefore, some graphical types likeQPixmap
,QImage
andQIcon
will never compare equal. Moreover, floating point numbers stored inQVariant
are no longer compared withqFuzzyCompare
, but instead use exact comparisons.
Furthermore, QVariant::operator<, QVariant::operator<=, QVariant::operator> and QVariant::operator>= were removed, because different variants are not always orderable. This also means that QVariant
cannot be used anymore as a key in a QMap
.
The QMetaType class#
In Qt 6, registration of comparators, and QDebug
and QDataStream
streaming operators is done automatically. Consequently, QMetaType::registerEqualsComparator()
, QMetaType::registerComparators()
, qRegisterMetaTypeStreamOperators()
and QMetaType::registerDebugStreamOperator()
do no longer exist. Calls to those methods have to be removed when porting to Qt 6.
Type registration#
Types used in Q_PROPERTY
have their meta-type stored in the class’ QMetaObject
. This requires the types to be complete when moc sees them, which can lead to compilation errors in code that worked in Qt 5. There are three ways to fix this issue:
Include the header which defines the type.
Instead of using an include, use the
Q_MOC_INCLUDE
macro. This helps if including the header would cause a cyclic dependency, or when it would slow down compilation.If the header is present in the cpp file which implements the class, it is also possible to include the moc generated file there.
Regular expression classes#
The QRegularExpression class#
In Qt 6, the QRegExp
type has been retired to the Qt5Compat module and all Qt APIs using it have been removed from other modules. Client code which used it can be ported to use QRegularExpression
in its place. As QRegularExpression
is present already in Qt 5, this can be done and tested before migration to Qt 6.
The QRegularExpression
class introduced in Qt 5 implements Perl-compatible regular expressions and is a big improvement upon QRegExp in terms of APIs offered, supported pattern syntax, and speed of execution. The biggest difference is that QRegularExpression
simply holds a regular expression, and it’s not modified when a match is requested. Instead, a QRegularExpressionMatch
object is returned, to check the result of a match and extract the captured substring. The same applies to global matching and QRegularExpressionMatchIterator
.
Other differences are outlined below.
Note
QRegularExpression
does not support all the features available in Perl-compatible regular expressions. The most notable one is the fact that duplicated names for capturing groups are not supported, and using them can lead to undefined behavior. This may change in a future version of Qt.
Different pattern syntax#
Porting a regular expression from QRegExp to QRegularExpression
may require changes to the pattern itself.
In specific scenarios, QRegExp was too lenient and accepted patterns that are simply invalid when using QRegularExpression
. These are easy to detect, because the QRegularExpression
objects built with these patterns are not valid (see isValid()
).
In other cases, a pattern ported from QRegExp to QRegularExpression
may silently change semantics. Therefore, it is necessary to review the patterns used. The most notable cases of silent incompatibility are:
Curly braces are needed to use a hexadecimal escape like
\xHHHH
with more than 2 digits. A pattern like\x2022
needs to be ported to\x{2022}
, or it will match a space (0x20
) followed by the string"22"
. In general, it is highly recommended to always use curly braces with the\x
escape, no matter the number of digits specified.A 0-to-n quantification like
{,n}
needs to be ported to{0,n}
to preserve semantics. Otherwise, a pattern such as\d{,3}
would match a digit followed by the exact string"{,3}"
.QRegExp by default does Unicode-aware matching, while
QRegularExpression
requires a separate option; see below for more details.c{.} in QRegExp does by default match all characters, including the newline character.
QRegularExpression
excludes the newline character by default. To include the newline character, set theDotMatchesEverythingOption
pattern option.
For an overview of the regular expression syntax supported by QRegularExpression
, please refer to the pcrepattern(3) man page, describing the pattern syntax supported by PCRE (the reference implementation of Perl-compatible regular expressions).
Porting from QRegExp::exactMatch()#
QRegExp::exactMatch() served two purposes: it exactly matched a regular expression against a subject string, and it implemented partial matching.
Porting from QRegExp’s Exact Matching#
Exact matching indicates whether the regular expression matches the entire subject string. For example, the classes yield on the subject string "abc123"
:
QRegExp::exactMatch()
"\\d+"
false
true
"[a-z]+\\d+"
true
true
Exact matching is not reflected in QRegularExpression
. If you want to be sure that the subject string matches the regular expression exactly, you can wrap the pattern using the anchoredPattern()
function:
p = QString("a .*|pattern") # re matches exactly the pattern string p re = QRegularExpression(QRegularExpression.anchoredPattern(p))
Porting from QRegExp’s Partial Matching#
When using QRegExp::exactMatch(), if an exact match was not found, one could still find out how much of the subject string was matched by the regular expression by calling QRegExp::matchedLength(). If the returned length was equal to the subject string’s length, then one could conclude that a partial match was found.
QRegularExpression
supports partial matching explicitly by means of the appropriate MatchType
.
Global matching#
Due to limitations of the QRegExp API, it was impossible to implement global matching correctly (that is, like Perl does). In particular, patterns that can match 0 characters (like "a*"
) are problematic.
globalMatch()
implements Perl global match correctly, and the returned iterator can be used to examine each result.
For example, if you have code like:
subject = QString("the quick fox") offset = 0 re = QRegExp("(\\w+)") while (offset = re.indexIn(subject, offset)) != -1: offset += re.matchedLength() # ...
You can rewrite it as:
subject = QString("the quick fox") re = QRegularExpression("(\\w+)") i = re.globalMatch(subject) while i.hasNext(): match = i.next() # ...
Unicode properties support#
When using QRegExp, character classes such as \w
, \d
, etc. match characters with the corresponding Unicode property: for instance, \d
matches any character with the Unicode Nd
(decimal digit) property.
Those character classes only match ASCII characters by default when using QRegularExpression
: for instance, \d
matches exactly a character in the 0-9
ASCII range. It is possible to change this behavior by using the UseUnicodePropertiesOption
pattern option.
Wildcard matching#
There is no direct way to do wildcard matching in QRegularExpression
. However, the wildcardToRegularExpression()
method is provided to translate glob patterns into a Perl-compatible regular expression that can be used for that purpose.
For example, if you have code like:
wildcard = QRegExp("*.txt") wildcard.setPatternSyntax(QRegExp.Wildcard)
You can rewrite it as:
wildcard = QRegularExpression(QRegularExpression.wildcardToRegularExpression("*.txt"))
Please note though that some shell-like wildcard patterns might not be translated to what you expect. The following example code will silently break if simply converted using the above-mentioned function:
fp1 = QString("C:/Users/dummy/files/content.txt") fp2 = QString("/home/dummy/files/content.txt") re1 = QRegExp("*/files/*") re1.setPatternSyntax(QRegExp.Wildcard) re1.exactMatch(fp1) # returns true re1.exactMatch(fp2) # returns true # but converted with QRegularExpression::wildcardToRegularExpression() re2 = QRegularExpression(QRegularExpression.wildcardToRegularExpression("*/files/*")) re2.match(fp1).hasMatch() # returns false re2.match(fp2).hasMatch() # returns false
This is because, by default, the regular expression returned by wildcardToRegularExpression()
is fully anchored. To get a regular expression that is not anchored, pass UnanchoredWildcardConversion
as the conversion options:
QRegularExpression re3(QRegularExpression.wildcardToRegularExpression( "*/files/*", QRegularExpression.UnanchoredWildcardConversion)) re3.match(fp1).hasMatch() # returns true re3.match(fp2).hasMatch() # returns true
Minimal matching#
QRegExp::setMinimal() implemented minimal matching by simply reversing the greediness of the quantifiers (QRegExp did not support lazy quantifiers, like *?
, +?
, etc.). QRegularExpression
instead does support greedy, lazy, and possessive quantifiers. The InvertedGreedinessOption
pattern option can be useful to emulate the effects of QRegExp::setMinimal(): if enabled, it inverts the greediness of quantifiers (greedy ones become lazy and vice versa).
Caret modes#
The AnchorAtOffsetMatchOption
match option can be used to emulate the QRegExp::CaretAtOffset behavior. There is no equivalent for the other QRegExp::CaretMode modes.
The QRegExp class#
In Qt6 QRegExp got removed from Qt Core. If your application cannot be ported right now, QRegExp
still exists in Qt5Compat to keep these code-bases working. If you want to use QRegExp
further, see Using the Qt5Compat module .
QEvent and subclasses#
The QEvent
class defined a copy constructor and an assignment operator, in spite of being a polymorphic class. Copying classes with virtual methods can result in slicing when assigning objects from different classes to each other. Since copying and assigning often happens implicilty, this could lead to hard-to-debug problems.
In Qt 6, the copy constructor and assignment operator for QEvent
subclasses have been made protected to prevent implicit copying. If you need to copy events, use the clone
method, which will return a heap-allocated copy of the QEvent
object. Make sure you delete the clone, perhaps using std::unique_ptr, unless you post it (in which case Qt will delete it once it has been delivered).
In your QEvent
subclasses, override clone(), and declare the protected and default-implemented copy constructor and assignment operator like this:
class MyEvent : public QEvent { public: // ... MyEvent *clone() const override { return new MyEvent(*this); } protected: MyEvent(const MyEvent &other) = default; MyEvent &operator=(const MyEvent &other) = default; MyEvent(MyEvent &&) = delete; MyEvent &operator=(MyEvent &&) = delete; // member data };
Note that if your MyEvent class allocates memory (e.g. through a pointer-to-implementation pattern), then you will have to implement custom copy semantics.
Serialization classes#
In Qt 6, QJsonDocument
methods for converting it to/from Qt’s legacy JSON binary format were removed in favor of the standardized CBOR format. Qt JSON types can be converted to Qt CBOR types, which can in turn be serialized into the CBOR binary format and vice versa. See, for example, fromJsonValue()
and toJsonValue()
.
If you still need to use the binary JSON format, you can use the replacements provided in the Qt5Compat module. They can be found in the QBinaryJson namespace. See Using the Qt5Compat module to find out how to use the module in your application.
Other classes#
In Qt 5, quit()
was equivalent to calling exit()
. This just exited the main event loop.
In Qt 6, the method will instead try to close all top-level windows by posting a close event. The windows are free to cancel the shutdown process by ignoring the event.
Call exit()
to keep the non-conditional behavior.
QLibraryInfo::location() and QLibraryInfo::Location were deprecated due to inconsistent naming. Use the new API path()
and LibraryPath
instead.
Qt State Machine Framework#
Qt State Machine was moved into the Qt SCXML module (soon to be renamed to Qt State Machines) and therefore it is no longer part of Qt Core. There were very few cross dependencies inside Qt Core which ultimately led to this decision.
Using the Qt5Compat module#
To use the Qt5Compat module, you need to build with its headers in your include path and link against its library. If you are using qmake , add the following to your .pro
file:
QT += core5compat
If you build your application or library using cmake, add the following to your CMakeList.txt
:
PUBLIC_LIBRARIES Qt::Core5Compat