'\0'
Qt 6 是努力使框架更高效,且更易于使用的结果。
为兼容每个发行的所有公共 API,我们试着维护二进制和源代码。但是,为使 Qt 成为更优框架,一些改变是不可避免的。
In this topic we summarize those changes in Qt Core, and provide guidance to handle them.
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.
实现为 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.
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.
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.
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
.
注意: 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.
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 代替。
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
将为
QList<MySmartPointer<MyLargeObject>>
as opposed to
QList<MyLargeObject>
in Qt 5.
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 不是 隐式共享 . 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.
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
.
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
字符串
"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
,
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 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
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);
'\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'
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 }
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 }
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:
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
类。
注意: Care must be taken to ensure that the referenced string data (for example, owned by a QString ) outlives the QStringView on all code paths.
注意:
若
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 += ...;
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
文件:
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
. It is also recommended to use
QStringView::tokenize
over
QStringView::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; }
In Qt 6, QRecursiveMutex 并非继承自 QMutex anymore. This change was done to improve the performance of both QMutex and QRecursiveMutex .
Due to those changes, the QMutex::RecursionMode enum has been removed, and QMutexLocker is now a templated class that can operate on both QMutex and QRecursiveMutex .
To avoid unintended usage of QFuture , there were some changes to QFuture API in Qt 6, which may introduce source compatibility breaks.
Conversion of
QFuture<T>
to
T
has been disabled. The casting operator was calling
QFuture::result
(), which may lead to undefined behavior if the user has moved the results from
QFuture
凭借
QFuture::takeResult
() before trying to do the conversion. Use
QFuture::result
() 或
QFuture::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> &)
构造函数:
QFuture<int> future = ... QFuture<void> voidFuture = QFuture<void>(future);
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()) // ...
In Qt 6, there were some improvements to QFuture and QFutureWatcher which caused the following behavioral changes:
pause()
or
setPaused(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 new
isSuspending()
and
isSuspended()
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.
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 undocumented
QFutureInterface
may be affected.
In Qt 6, the new QPromise class should be used instead of unofficial QFutureInterface as a "setter" counterpart of QFuture .
在 Qt 6, QProcess::start () overload that interprets a single command string by splitting it into program name and arguments is renamed to QProcess::startCommand (). However, a QProcess::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
QProcess::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.
QVariant
has been rewritten to use
QMetaType
for all of its operations. This implies behavior changes in a few methods:
QVariant::isNull()
now only returns
true
若
QVariant
is empty or contains a
nullptr
. In Qt 5, it also returned true for classes in qtbase which had an
isNull
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, as
isNull()
is rarely the property one is interested in (compare
QString::isEmpty()
/
isNull()
and
QTime::isValid
/
isNull
).
QVariant::operator==
使用
QMetaType::equals
in Qt 6. Therefore, some graphical types like
QPixmap
,
QImage
and
QIcon
will never compare equal. Moreover, floating point numbers stored in
QVariant
are no longer compared with
qFuzzyCompare
, 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 .
In Qt 6, registration of comparators, and cQDebug 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.
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:
Q_MOC_INCLUDE
macro. This helps if including the header would cause a cyclic dependency, or when it would slow down compilation.
In Qt6, all methods taking the
QRegExp
got removed from our code-base. Therefore it is very likely that you will have to port your application or library to
QRegularExpression
.
QRegularExpression implements Perl-compatible regular expressions. It fully supports Unicode. For an overview of the regular expression syntax supported by QRegularExpression , please refer to the aforementioned pcrepattern(3) man page. A regular expression is made up of two things: a pattern string and a set of pattern options that change the meaning of the pattern string.
There are some subtle differences between
QRegularExpression
and
QRegExp
that will be explained by this document to ease the porting effort.
QRegularExpression is more strict when it comes to the syntax of the regular expression. Therefore it is always good to check the expression for validity .
QRegularExpression
can almost always be declared const (except when the pattern changes), while
QRegExp
almost never could be.
There is no replacement for the CaretMode enumeration. The QRegularExpression::AnchoredMatchOption match option can be used to emulate the QRegExp::CaretAtOffset behavior. There is no equivalent for the other QRegExp::CaretMode 模式。
QRegularExpression supports only Perl-compatible regular expressions. Still, it 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.
There is no direct way to do wildcard matching in QRegularExpression 。不管怎样, QRegularExpression::wildcardToRegularExpression method is provided to translate glob patterns into a Perl-compatible regular expression that can be used for that purpose.
例如,若有代码像
QRegExp wildcard("*.txt"); wildcard.setPatternSyntax(QRegExp::Wildcard);
可以把它重写成
auto wildcard = QRegularExpression(QRegularExpression::wildcardToRegularExpression("*.txt"));
Please note though that not all shell like wildcard pattern might be translated in a way you would expect it. The following example code will silently break if simply converted using the above mentioned function:
const QString fp1("C:/Users/dummy/files/content.txt"); const QString fp2("/home/dummy/files/content.txt"); QRegExp re1("*/files/*"); re1.setPatternSyntax(QRegExp::Wildcard); ... = re1.exactMatch(fp1); // returns true ... = re1.exactMatch(fp2); // returns true // but converted with QRegularExpression::wildcardToRegularExpression() QRegularExpression re2(QRegularExpression::wildcardToRegularExpression("*/files/*")); ... = re2.match(fp1).hasMatch(); // returns false ... = re2.match(fp2).hasMatch(); // returns false
Forward searching inside a string was usually implemented with a loop using
QRegExp::indexIn
and a growing offset, but can now be easily implemented with
QRegularExpressionMatchIterator
or
QString::indexOf
.
例如,若有代码像
QString subject("the quick fox"); int offset = 0; QRegExp re("(\\w+)"); while ((offset = re.indexIn(subject, offset)) != -1) { offset += re.matchedLength(); // ... }
可以把它重写成
QRegularExpression re("(\\w+)"); QString subject("the quick fox"); QRegularExpressionMatchIterator i = re.globalMatch(subject); while (i.hasNext()) { QRegularExpressionMatch match = i.next(); // ... } // or alternatively using QString::indexOf qsizetype from = 0; QRegularExpressionMatch match; while ((from = subject.indexOf(re, from, &match)) != -1) { from += match.capturedLength(); // ... }
Backwards searching inside a string was usually often implemented as a loop over
QRegExp::lastIndexIn
, but can now be easily implemented using
QString::lastIndexOf
and
QRegularExpressionMatch
.
注意: QRegularExpressionMatchIterator is not capable of performing a backwards search.
例如,若有代码像
int offset = -1; QString subject("Lorem ipsum dolor sit amet, consetetur sadipscing."); QRegExp re("\\s+([ids]\\w+)"); while ((offset = re.lastIndexIn(subject, offset)) != -1) { --offset; // ... }
可以把它重写成
qsizetype from = -1; QString subject("Lorem ipsum dolor sit amet, consetetur sadipscing."); QRegularExpressionMatch match; QRegularExpression re("\\s+([ids]\\w+)"); while ((from = subject.lastIndexOf(re, from, &match)) != -1) { --from; // ... }
QRegExp::exactMatch
served two purposes: it exactly matched a regular expression against a subject string, and it implemented partial matching. Exact matching indicates whether the regular expression matches the entire subject string. For example:
QString source("abc123"); QRegExp("\\d+").exactMatch(source); // returns false QRegExp("[a-z]+\\d+").exactMatch(source); // returns true QRegularExpression("\\d+").match(source).hasMatch(); // returns true QRegularExpression("[a-z]+\\d+").match(source).hasMatch(); // returns 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 QRegularExpression::anchoredPattern 函数:
QString source("abc123"); QString pattern("\\d+"); QRegularExpression(pattern).match(source).hasMatch(); // returns true pattern = QRegularExpression::anchoredPattern(pattern); QRegularExpression(pattern).match(source).hasMatch(); // returns false
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
QRegularExpression::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).
Porting a regular expression from
QRegExp
to
QRegularExpression
may require changes to the pattern itself. Therefore it is recommended to check the pattern used with the
QRegularExpression::isValid
method. This is especially important for user provided pattern or pattern not controlled by the developer.
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:
\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 amount of digits specified.
0-to-n
quantification like
{,n}
needs to be ported to
{0,n}
to preserve semantics. Otherwise, a pattern such as
\d{,3}
would actually match a digit followed by the exact string
"{,3}"
.
当使用
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
QRegularExpression::MatchType
.
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 zero characters (like "a*") are problematic.
QRegularExpression::wildcardToRegularExpression
implements Perl global match correctly, and the returned iterator can be used to examine each result.
当使用
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
QRegularExpression::UseUnicodePropertiesOption
模式选项。
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, you need to link against the new Qt5Compat module and add this line to your
qmake
.pro
文件:
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
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).
在 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.
In Qt 5, QCoreApplication::quit () was equivalent to calling QCoreApplication::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.
调用 QCoreApplication::exit () to keep the non-conditional behavior.
QLibraryInfo::location() and QLibraryInfo::Location were deprecated due to inconsistent naming. Use the new API QLibraryInfo::path () 和 QLibraryInfo::LibraryPath 代替。