本页内容

变更内容Qt Core

Qt 6 的变更,是出于让框架更高效、更易于使用的有意识努力。

我们力求在每个版本中保持所有公共 API 的二进制和源代码兼容性。但为了使 Qt 成为更好的框架,某些变更在所难免。

在本主题中,我们将总结Qt Core 中的这些变更,并提供处理这些变更的指导。

容器类

QHash、QMultiHash、QSet

qHash() 签名

对于自定义类型,QHash 和QMultiHash 依赖于您在同一命名空间中提供一个custom qHash() function 。在 Qt 4 和 Qt 5 中,qHash 函数的返回值和可选的第二个参数类型为uint 。而在 Qt 6 中,其类型变为size_t 。

也就是说,您需要将

uint qHash(MyType x, uint seed);

为

size_t qHash(MyType x, size_t seed);

这使得在 64 位平台上,QHash 、QMultiHash 和QSet 能够容纳超过 2^32 个项目。

引用稳定性

Qt 6 中QHash 、QMultiHash 和QSet 的实现方式已从基于节点的方案改为两阶段查找表。这种设计既能将哈希实例的内存开销保持在极低水平,又能提供良好的性能。

需要注意的一个行为变化是:当哈希表需要扩容或删除条目时,新实现将不再为哈希表中的元素提供稳定的引用。依赖此类稳定性的应用程序现在可能会遇到未定义的行为。

移除 QHash::insertMulti

在 Qt 5 中,QHash 可以通过 QHash::insertMulti 创建多值哈希,且QMultiHash 是从QHash 派生而来的。

在 Qt 6 中,这两种类型及其用例已截然不同,因此 QHash::insertMulti 已被移除。

QVector、QList

在 Qt 6 之前,QVector 和QList 是两个独立的类。在 Qt 6 中,它们已被统一:Qt 5 中QList 的实现已被移除,这两个类现在都使用更新后的QVector 实现。QList 是包含实际实现的类,而QVector 是QList 的别名(typedef)。

QList在 Qt 6 中,'s fromVector() 和 toVector(),以及QVector 的 fromList() 和 toList(),不再涉及数据复制。它们现在返回被调用时所针对的对象。

API 变更

QList的(因此也包括QVector 的)size 类型已从int 更改为qsizetype 。随着 size 类型的变更,所有相关方法的签名均已更新为使用qsizetype 。这使得QList 在 64 位平台上能够容纳超过 2^31 个项目。

在将代码库升级到 Qt 6 时,此 API 变更很可能会导致编译器发出关于类型缩窄转换的警告。假设有以下示例代码:

void myFunction(QList<MyType> &data) {
    int size = data.size();
    // ...
    const int pos = getInsertPosition(size);
    data.insert(pos, MyType());
    // ...
}

则需要将其更新为使用qsizetype 或 auto 关键字:

void myFunction(QList<MyType> &data) {
    auto size = data.size();
    // ...
    const auto pos = getInsertPosition(size);
    data.insert(pos, MyType());
    // ...
}

或者,您也可以使用类型强制转换,将所有类型强制转换为int 或qsizetype 。

注意:若需 同时针对 Qt 5 和 Qt 6 进行构建,使用 `auto` 关键字是解决不同版本间签名差异的理想方案。

内存布局

QList 在 Qt 6 中,与内存布局相关的多项变更已得到落实。

在 Qt 5 中,sizeof(QList<T>) 的大小等同于一个指针的大小。现在,多余的指针间接引用已被移除,QList 的数据成员直接存储在对象中。默认情况下,sizeof(QList<T>) 的大小应等同于 3 个指针的大小。

与此同时,元素的内存布局也已更新。QList 现在总是将其元素直接存储在已分配的内存区域中,这与 Qt 5 不同——在 Qt 5 中,某些对象会单独在堆上分配,而指向这些对象的指针则被放入QList 中。

请注意,后者尤其会影响大型对象。若要实现与 Qt 5 相同的行为,您可以将对象包装为智能指针,并将这些智能指针直接存储在QList 中。在这种情况下,您的QList 的类型应为QList<MySmartPointer<MyLargeObject>> ,而非 Qt 5 中的QList<MyLargeObject> 。

引用稳定性

QVector/QList 的实现进行了多项更改。与QVector 相关的一项是:优化了在列表开头的插入操作(类似于 Qt 5 中的QList )。与QList 相关的一项是:简化了元素的内存布局。

重要提示:这些 更改会影响引用(reference)的稳定性。在 Qt 6 中,您应考虑通过任何修改大小或容量的方法来使所有引用失效,即使QList 并非隐式共享。此规则的例外情况已在文档中明确说明。

依赖于特定引用稳定性的应用程序在升级至 Qt 6 时可能会遇到未定义行为。您应特别注意那些最初使用了具有非 C 兼容数组布局的 `QVector ` 或 `QList ` 的情况。

Qt6 中的视图类

概述

Qt6 引入了若干新的View 类。除了已有的QStringView 之外,现在还新增了QByteArrayView ,随后是专门化的QUtf8StringView 以及更通用的QAnyStringView 。

以 QStringView 为例介绍视图类

QStringView 类通过QString API的只读子集,为UTF-16字符串提供统一的视图。与QString 不同(后者会保留字符串的独立副本,可能采用引用计数机制),QStringView 则提供对存储在其他位置的字符串的视图。

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." 存储在二进制文件中,不会在运行时分配内存。view 仅是字符串"Hello." 的视图,因此无需创建副本。当复制QStringView 时,viewToView 观察到的字符串与被复制的view 所观察到的字符串相同。这意味着viewToView 无需创建副本或进行原子递增操作。它们都是对现有字符串"Hello." 的视图。

作为函数参数的视图

视图应按值传递,而非按常量引用传递。

void myfun1(QStringView sv);        // preferred
void myfun2(const QStringView &sv); // compiles and works, but slower

视图操作函数

QStringView 支持允许我们操作字符串视图的函数。这使我们能够在不创建被查看字符串的部分副本的情况下修改该视图。

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 同时支持以空字符结尾和未以空字符结尾的字符串。区别在于初始化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'

视图的所有权模型

由于views 并不拥有其引用的内存,因此必须确保在所有代码路径中,被引用的数据(例如由QString 拥有的数据)的存活时间都长于view 。

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 转换为 QString

QStringView 不会隐式或显式地转换为QString ,但会创建其数据的深拷贝:

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
}

重要注意事项

通过利用新的视图类,可以在许多用例中显著提升性能。但是,需要注意的是,其中可能存在一些注意事项。因此,请务必记住:

  • 视图应按值传递,而非按常量引用传递。
  • 使用负长度构造视图属于未定义行为。
  • 必须确保在所有代码路径中,被引用的数据(例如由QString 拥有的数据)的存活时间都长于视图。

QStringView 类

从 Qt6 开始,通常建议使用 `QStringView ` 而不是 `QStringRef`。`QStringView ` 引用其不拥有的 UTF-16 字符串中的一段连续区域。它作为各种 UTF-16 字符串的接口类型,无需先构造 `QString `。QStringView 类公开了 `QString ` 以及此前已存在的 `QStringRef ` 类中的几乎所有只读方法。

注意:必须确保 在所有代码路径中,被引用的字符串数据(例如由QString 拥有的数据)的存活时间都长于QStringView 。

注意:如果 QStringView 封装了QString ,则需要格外小心,因为与QStringRef 不同,一旦QString 数据发生重定位,QStringView 不会更新内部数据指针。

QString string = ...;
QStringView view{string};

// Appending something very long might cause a relocation and will
// ultimately result in a garbled QStringView.
string += ...;

QStringRef 类

在 Qt6 中,QStringRef 已从Qt Core 中移除。为了便于现有应用程序的移植而无需修改整个代码库,QStringRef 类并未完全消失,而是被移至 Qt5Compat 模块中。若您希望继续使用QStringRef ,请参阅《使用 Qt5Compat 模块》。

遗憾的是,QString 暴露的某些返回QStringRef 的方法无法迁移到 Qt5Compat。因此可能需要进行一些手动移植。如果您的代码使用了以下一个或多个函数,则需要将其移植为使用QStringView 或QStringTokenizer 。此外,对于性能关键型代码,建议使用QStringView::tokenize 代替QStringView::split 。

将使用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;

改为:

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;
}

在 Qt 6 中,QRecursiveMutex 不再继承自QMutex 。此更改旨在提升QMutex 和QRecursiveMutex 的性能。

由于这些变更,QMutex::RecursionMode 枚举已被移除,QMutexLocker 现已成为一个模板类,可同时操作QMutex 和QRecursiveMutex 。

QFuture 类

为避免QFuture 被意外使用,Qt 6 对QFuture 的 API 进行了一些更改,这可能会导致源代码兼容性中断。

QFuture 与其他类型之间的隐式转换

已禁用将QFuture<T> 转换为T 的操作。该强制类型转换运算符原本会调用QFuture::result(),如果用户在尝试转换之前已通过QFuture::takeResult() 将QFuture 中的结果进行了移动,则可能会导致未定义行为。在需要将QFuture<T> 转换为T 时,请显式使用QFuture::result() 或QFuture::takeResult() 方法。

从QFuture<T> 到QFuture<void> 的隐式转换也已被禁用。如果您确实需要进行转换,请使用显式的QFuture<void>(const QFuture<T> &) 构造函数:

QFuture<int> future = ...
QFuture<void> voidFuture = QFuture<void>(future);

相等运算符

已移除了QFuture 的相等运算符。这些运算符原本比较的是底层的 d-指针,而非比较结果本身,这可能与用户的预期不符。若需比较QFuture 对象,请使用QFuture::result() 或QFuture::takeResult() 方法。例如:

QFuture<int> future1 = ...;
QFuture<int> future2 = ...;
if (future1.result() == future2.result())
    // ...

QFuture 和 QFutureWatcher 的行为变更

在 Qt 6 中,对QFuture 和QFutureWatcher 进行了一些改进,这导致了以下行为变化:

  • 暂停QFuture 或QFutureWatcher 之后(通过调用pause() 或setPaused(true) ),QFutureWatcher 不会立即停止发送进度和结果就绪信号。在暂停的瞬间,可能仍有正在进行的计算无法被停止。 此类计算的信号可能会在暂停后继续发送,而非被推迟并仅在下次恢复后才报告。若要获取暂停实际生效时的通知,可使用QFutureWatcher::suspended() 信号。此外,还新增了isSuspending() 和isSuspended() 方法,用于检查QFuture 是否正在暂停过程中,或是否已处于暂停状态。 请注意,出于一致性考虑,无论是QFuture 还是QFutureWatcher ,与暂停相关的 API 均已被废弃,并替换为名称中包含“suspend”的类似方法。
  • QFuture::waitForFinished() 现将等待直至QFuture 实际进入 finished 状态,而非在脱离 running 状态后立即退出。这可防止在调用waitForFinished() 时,若未来对象尚未启动,该方法立即退出。QFutureWatcher::waitForFinished() 同样适用此规则。此更改不会影响原使用QFuture 配合QtConcurrent 的代码行为。仅那些配合未记录的QFutureInterface 使用的代码可能会受到影响。
  • QFutureWatcher::isFinished() 现在反映QFuture 的完成状态,而不是在QFutureWatcher::finished() 被触发之前返回 false。

QPromise 类

在 Qt 6 中,应使用新的QPromise 类代替非官方的 QFutureInterface,作为QFuture 的“设置器”对应类。

IO 类

QProcess 类

在 Qt 6 中,将单个命令字符串拆分为程序名和参数的QProcess::start() 重载方法已重命名为QProcess::startCommand()。不过,仍然存在一个接受单个字符串的QProcess::start() 重载方法,以及用于处理参数的QStringList 。 由于 `QStringList ` 参数的默认值为空列表,仅传递字符串的现有代码仍可编译,但若该字符串是包含参数的完整命令字符串,则无法成功执行该进程。

Qt 5.15 为该重载方法引入了弃用警告,以便于发现并更新现有代码:

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() 和 Q_PID 类型;请改用QProcess::processId() 获取本机进程标识符。使用本机 Win32 API 将 Q_PID 中的数据作为 Win32PROCESS_INFORMATION 结构体进行访问的代码不再受支持。

元类型系统

QVariant 类

QVariant 已重写,使其所有操作均使用QMetaType 。这意味着某些方法的行为发生了变化:

  • QVariant::isNull() 现在仅当QVariant 为空或包含nullptr 时,才返回true 。在 Qt 5 中,对于 qtbase 中的类,如果其自身的isNull 方法返回 true,该方法也会返回 true。 依赖旧行为的代码需要检查所包含的值是否返回 isNull——不过此类代码在实际中不太可能出现,因为isNull() 很少是开发者关注的属性(参见QString::isEmpty() /isNull() 与QTime::isValid /isNull )。
  • QVariant::operator== Qt 6 中使用QMetaType::equals 。因此,某些缺乏合适相等运算符的类型(如QPixmap 或QIcon )将永远无法被判定为相等。此外,存储在QVariant 中的浮点数不再与qFuzzyCompare 进行比较,而是使用精确比较。

此外,已移除了 QVariant::operator<、QVariant::operator<=、QVariant::operator> 和 QVariant::operator>=,因为不同的变体类型并不总是可排序的。这也意味着QVariant 不能再作为QMap 中的键使用了。

QMetaType 类

在 Qt 6 中,比较器的注册以及QDebug 和QDataStream 流操作符的注册均由系统自动完成。因此,QMetaType::registerEqualsComparator() 、QMetaType::registerComparators() 、qRegisterMetaTypeStreamOperators() 和QMetaType::registerDebugStreamOperator() 已不再存在。在向 Qt 6 迁移时,必须移除对这些方法的调用。

类型注册

Q_PROPERTY 中使用的类型,其元类型存储在类的QMetaObject 中。这要求当 moc 检测到这些类型时,它们必须是完整的,这可能会导致在 Qt 5 中能正常运行的代码出现编译错误。有三种方法可以解决此问题:

  • 包含定义该类型的头文件。
  • 避免使用包含语句,转而使用Q_MOC_INCLUDE 宏。当包含头文件会导致循环依赖,或者会降低编译速度时,此方法尤为有效。
  • 如果实现该类的 cpp 文件中已包含该头文件,也可以在该处包含 moc 生成的文件。

正则表达式类

QRegularExpression 类

在 Qt 6 中,QRegExp 类型已被移至 Qt5Compat 模块,并且所有使用该类型的 Qt API 均已从其他模块中移除。使用该类型的客户端代码可移植为改用QRegularExpression 代替。由于QRegularExpression 已在 Qt 5 中存在,因此可在迁移至 Qt 6 之前完成此操作并进行测试。

Qt 5 中引入的QRegularExpression 类实现了与 Perl 兼容的正则表达式,在提供的 API、支持的模式语法以及执行速度方面,相较于QRegExp 都有了显著改进。 最大的区别在于,QRegularExpression 仅用于存储正则表达式,当请求匹配时不会对其进行修改。取而代之的是,它会返回一个QRegularExpressionMatch 对象,用于检查匹配结果并提取捕获的子字符串。全局匹配和QRegularExpressionMatchIterator 也是如此。

其他差异概述如下。

注意: QRegularExpression 并不支持Perl兼容正则表达式中的所有功能。最值得注意的一点是,不支持捕获组名称重复,使用重复名称可能会导致未定义的行为。这一点可能会在Qt的未来版本中有所改变。

不同的模式语法

将正则表达式从QRegExp 移植到QRegularExpression 时,可能需要对模式本身进行修改。

在特定情况下,QRegExp 的规则过于宽松,会接受一些在QRegularExpression 中完全无效的模式。这些情况很容易被检测到,因为使用这些模式构建的QRegularExpression 对象是无效的(参见QRegularExpression::isValid())。

在其他情况下,从QRegExp 移植到QRegularExpression 的模式可能会悄然改变语义。因此,有必要审查所使用的模式。最显著的隐式不兼容情况包括:

  • 若要使用\xHHHH 这种超过 2 位的十六进制转义序列,则需要使用花括号。类似\x2022 的模式需要移植为\x{2022} ,否则它将匹配一个空格(0x20 )后跟字符串"22" 。一般而言,强烈建议在\x 转义序列中始终使用花括号,无论指定的位数是多少。
  • 类似{,n} 的 0 到 n 量化表达式需要转换为{0,n} 才能保持语义一致。否则,诸如\d{,3} 这样的模式将匹配一个数字后跟精确字符串"{,3}" 。
  • QRegExp 默认情况下会进行支持 Unicode 的匹配,而QRegularExpression 则需要单独的选项;更多详情请参见下文。
  • 在QRegExp 中,c{.} 默认匹配所有字符,包括换行符。QRegularExpression 默认排除换行符。若要包含换行符,请设置QRegularExpression::DotMatchesEverythingOption 模式选项。

有关QRegularExpression 支持的正则表达式语法概述,请参阅pcrepattern(3)手册页,其中描述了 PCRE(Perl 兼容正则表达式的参考实现)支持的模式语法。

从 QRegExp::exactMatch() 移植而来

QRegExp::exactMatch() 具有双重功能:它既能将正则表达式与目标字符串进行精确匹配,又能实现部分匹配。

从 QRegExp 的精确匹配移植

精确匹配指正则表达式是否与整个目标字符串完全匹配。例如,针对目标字符串"abc123" ,以下类会返回结果:

QRegExp::exactMatch()QRegularExpressionMatch::hasMatch()
"\\d+"falsetrue
"[a-z]+\\d+"truetrue

QRegularExpression 中不支持精确匹配。若要确保待匹配字符串与正则表达式完全匹配,可以使用QRegularExpression::anchoredPattern()函数将模式进行包装:

QString p("a .*|pattern");

// re matches exactly the pattern string p
QRegularExpression re(QRegularExpression::anchoredPattern(p));
从 QRegExp 的部分匹配功能移植而来

在使用QRegExp::exactMatch() 时,如果未找到精确匹配,仍可通过调用QRegExp::matchedLength() 来确定正则表达式匹配了目标字符串中的多少内容。如果返回的长度等于目标字符串的长度,则可以推断为找到了部分匹配。

QRegularExpression 通过相应的QRegularExpression::MatchType ,显式支持部分匹配。

全局匹配

由于QRegExp API 的限制,无法正确实现全局匹配(即像 Perl 那样)。特别是,能够匹配 0 个字符的模式(如"a*" )会带来问题。

QRegularExpression::globalMatch() 正确实现了 Perl 的全局匹配,且返回的迭代器可用于检查每个匹配结果。

例如,如果你有如下代码:

QString subject("the quick fox");

int offset = 0;
QRegExp re("(\\w+)");
while ((offset = re.indexIn(subject, offset)) != -1) {
    offset += re.matchedLength();
    // ...
}

你可以将其重写为:

QString subject("the quick fox");

QRegularExpression re("(\\w+)");
QRegularExpressionMatchIterator i = re.globalMatch(subject);
while (i.hasNext()) {
    QRegularExpressionMatch match = i.next();
    // ...
}

Unicode 属性支持

使用QRegExp 时,诸如\w 、\d 等字符类会匹配具有相应 Unicode 属性的字符:例如,\d 会匹配任何具有 UnicodeNd (十进制数字)属性的字符。

在使用QRegularExpression 时,这些字符类默认仅匹配ASCII字符:例如,\d 仅精确匹配0-9 ASCII范围内的字符。可通过使用QRegularExpression::UseUnicodePropertiesOption 模式选项来更改此行为。

通配符匹配

在QRegularExpression 中没有直接实现通配符匹配的方法。不过,提供了QRegularExpression::wildcardToRegularExpression()方法,用于将glob模式转换为Perl兼容的正则表达式,从而实现该功能。

例如,如果你有如下代码:

QRegExp wildcard("*.txt");
wildcard.setPatternSyntax(QRegExp::Wildcard);

你可以将其重写为:

auto wildcard = QRegularExpression(QRegularExpression::wildcardToRegularExpression("*.txt"));

但请注意,某些类似 shell 的通配符模式可能无法转换为你预期的结果。如果仅使用上述函数进行转换,以下示例代码会悄无声息地出错:

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

这是因为默认情况下,QRegularExpression::wildcardToRegularExpression() 返回的正则表达式是完全锚定的。要获得一个非锚定的正则表达式,请将QRegularExpression::UnanchoredWildcardConversion 作为转换选项传入:

QRegularExpression re3(QRegularExpression::wildcardToRegularExpression(
                           "*/files/*", QRegularExpression::UnanchoredWildcardConversion));
re3.match(fp1).hasMatch(); // returns true
re3.match(fp2).hasMatch(); // returns true

最小匹配

QRegExp::setMinimal() 通过简单地反转量词的贪婪性来实现最小匹配(QRegExp 不支持懒惰量词,如*? 、+? 等)。而QRegularExpression 则支持贪婪、懒惰和占有量词。QRegularExpression::InvertedGreedinessOption 模式选项可用于模拟QRegExp::setMinimal()的效果:若启用该选项,将反转量词的贪婪性(贪婪的量词变为懒惰的,反之亦然)。

Caret 模式

QRegularExpression::AnchorAtOffsetMatchOption 匹配选项可用于模拟QRegExp::CaretAtOffset 的行为。其他QRegExp::CaretMode 模式目前尚无等效实现。

QRegExp 类

在 Qt6 中,已从Qt Core 中移除了QRegExp 。如果您的应用程序目前无法移植,QRegExp 仍存在于 Qt5Compat 中,以确保这些代码库能够继续运行。如果您希望继续使用QRegExp ,请参阅《使用 Qt5Compat 模块》。

QEvent 及其子类

尽管QEvent 类是一个多态类,但它定义了复制构造函数和赋值运算符。复制包含虚方法的类时,在将不同类的对象相互赋值时可能会导致“切片”现象。由于复制和赋值通常是隐式进行的,这可能会导致难以调试的问题。

在 Qt 6 中,QEvent 子类的复制构造函数和赋值运算符已被设为受保护(protected),以防止隐式复制。 若需复制事件,请使用clone 方法,该方法将返回一个堆分配的QEvent 对象副本。请确保删除该克隆对象(例如使用std::unique_ptr),除非您将其发布(在此情况下,Qt XML会在事件被分发后自动删除它)。

在您的QEvent 子类中,请重写 clone() 方法,并像这样声明受保护且默认实现的复制构造函数和赋值运算符:

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
};

请注意,如果您的 MyEvent 类分配了内存(例如通过“指向实现的指针”模式),则必须实现自定义的复制语义。

序列化类

在 Qt 6 中,用于将数据与 Qt 旧版 JSON 二进制格式相互转换的 `QJsonDocument ` 方法已被移除,取而代之的是标准化的 CBOR 格式。Qt JSON 类型可以转换为 Qt CBOR 类型,后者进而可以序列化为 CBOR 二进制格式,反之亦然。例如,请参阅QCborValue::fromJsonValue() 和QCborValue::toJsonValue()。

如果您仍需使用二进制 JSON 格式,可以使用 Qt5Compat 模块中提供的替代方案。这些方法位于QBinaryJson 命名空间中。请参阅《使用 Qt5Compat 模块》以了解如何在您的应用程序中使用该模块。

其他类

在 Qt 5 中,QCoreApplication::quit() 与调用QCoreApplication::exit() 效果相同。这仅会退出主事件循环。

在 Qt 6 中,该方法会通过发布关闭事件来尝试关闭所有顶级窗口。窗口可以通过忽略该事件来取消关闭过程。

若要保留非条件行为,请调用QCoreApplication::exit()。

由于命名不一致,QLibraryInfo::location() 和 QLibraryInfo::Location 已被废弃。请改用新的 APIQLibraryInfo::path() 和QLibraryInfo::LibraryPath 。

Qt State Machine Framework

Qt State Machine 已移至Qt SCXML 模块(即将更名为 Qt State Machine),因此不再属于Qt Core 模块。Qt Core 内部的跨模块依赖关系极少,这最终促成了这一决定。

使用 Qt5Compat 模块

要使用Qt5Compat模块,您需要在包含路径中添加其头文件,并在链接时使用其库。如果您使用qmake,请在您的.pro 文件中添加以下内容:

QT += core5compat

如果您使用cmake 构建应用程序或库,请在CMakeList.txt 中添加以下内容:

PUBLIC_LIBRARIES
    Qt::Core5Compat

QTextStream

已移除 QTextStream::setCodec()。请改用QTextStream::setEncoding() 并配合新的 Encoding 枚举。

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.