(由于排版限制 可能下文不方便阅读 ,可点击https://www.evget.com/article/2020/10/26/38826.html阅读原文!)
您可能知道,Qt有一个元类型系统,该系统提供有关类型的运行时动态信息。它可以将您的类型存储在QVariant中,并在信号插槽系统中排成队列,并在整个QML引擎中使用。在即将发布的Qt 6.0版本中,我们借此机会重新审视了它的基础知识,并利用了C ++ 17为我们提供的功能。在下文中,我们将检查这些更改,并说明它们如何影响您的项目。
QMetaType更加了解您的类
在Qt 5中,QMetaType包含默认构造一个类,复制它并销毁它所必需的信息。此外,它知道如何将其保存到QDataStream以及从QDataStream加载它,并存储了一些标志来描述它的各种属性(例如,类型是否琐碎,枚举等)。另外,它将存储该类型的QMetaObject(如果有的话)和一个数字ID,以标识该类型以及类型名称。
最后,QMetaType包含用于比较某种(元)类型的对象,进行打印qDebug以及从一种类型转换为另一种类型的功能。但是,您必须使用QMetaType::registerComparators()QMetaType中的和其他静态寄存器函数才能真正利用该功能。这会将指向这些函数的指针放入相应的注册表中,基本上是从元类型ID到函数指针的映射。
在Qt 6中,我们做的第一件事就是扩展QMetaType中存储的信息。现代C++已经有将近10年的历史了,所以是时候在QMetaType中存储移动构造函数的信息了。而且为了更好地支持过度对齐的类型,我们现在也存储了你的类型的对齐要求。此外,我们认为注册表有点笨拙。毕竟,我们为什么要要求你调用QMetaType::registerEqualsComparator(),而我们已经可以通过简单地查看类型来知道这一点?所以在 Qt 6 中,QMetaType::registerEqualsComparator、QMetaType::registerComparators、qRegisterMetaTypeStreamOperators 和 QMetaType::registerDebugStreamOperator 已经被删除。元类型系统会自动知道这些。这里的例外是QMetaType::registerConverterFunction。相反,元类型系统将自动知道这些信息。这里的离群值是
QMetaType::registerEqualsComparatorQMetaType::registerComparatorsqRegisterMetaTypeStreamOperatorsQMetaType::registerDebugStreamOperatorQMetaType::registerConverterFunction。
由于无法可靠地知道应该使用哪些函数进行转换,并且我们允许注册基本上任意的转换,因此该功能与Qt 5中的相同。
通过这些更改,我们还可以统一处理Qt内部类型和用户注册的类型:这意味着例如QMetaType::compare现在可以使用int:
#include#includeint main() { int i = 1; int j = 2; int result = 0; const bool ok = QMetaType::compare(&i, &j, QMetaType::Int, &result); if (ok) { // prints -1 as expected in Qt 6 qDebug() << result; } else { // This would get printed in Qt 5 qDebug() << "Cannot compare integer with QMetaType :-("; } }
QMetaType在编译时知道您的类型
多亏了C++反思能力的各种进步,我们现在可以在编译时从一个类型中获得我们需要的所有信息--包括它的名字。在 Qt 中,我们使用了一个非常类似的方法,尽管对旧编译器进行了某些扩展和变通。但比实现更有趣的是它对你意味着什么。首先,我们不需要通过以下两种方式创建 QMetaType
QMetaType oldWay1 = QMetaType::fromName("KnownTypeName");
或者
QMetaType oldWay2(knownTypeID);
现在建议您使用以下命令创建QMetaType
QMetaType newWay = QMetaType::fromType();
如果你知道类型。其他方法仍然存在,当你在编译时不知道类型时,这些方法是有用的。然而,fromType 避免了在运行时从 id/name 到 QMetaType 的一次查找。请注意,从 Qt 5.15 开始,你已经可以使用 fromType 了,但它仍然会进行一次查找。此外,你不能复制QMetaType,这限制了它的实用性,使它更方便地传递类型id。然而,在 Qt 6 中,QMetaType 是可以复制的。
你现在可能会问,这对 Q_DECLARE_METATYPE 和 qRegisterMetaType 意味着什么。毕竟,如果我们可以在编译时创建QMetaTypes,我们真的需要它们吗?
我们先来看一个例子。
#include#include#includestruct MyType { int i = 42; friend QDebug operator<<(QDebug dbg, MyType t) { QDebugStateSaver saver(dbg); dbg.nospace() << "MyType with i = " << t.i; return dbg; } }; int main() { MyType myInstance; QVariant var = QVariant::fromValue(myInstance); qDebug() << var; }
在Qt 5中,这将导致以下带有gcc的错误消息(+有关实例化失败的更多警告):
/usr/include/qt/QtCore/qmetatype.h: In instantiation of 'constexprint qMetaTypeId() [with T = MyType]':/usr/include/qt/QtCore/qvariant.h:371:37: required from 'static QVariantQVariant::fromValue(const T&) [with T = MyType]'test.cpp:16:48: required from here/usr/include/qt/QtCore/qglobal.h:121:63: error: static assertion failed: Type isnot registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt'smeta-object system 121 | # define Q_STATIC_ASSERT_X(Condition, Message) static_assert(bool(Condition), Message) |^~~~~~~~~~~~~~~/usr/include/qt/QtCore/qmetatype.h:1916:5: note: in expansion of macro 'Q_STATIC_ASSERT_X' 1916 | Q_STATIC_ASSERT_X(QMetaTypeId2::Defined, "Type is not registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt's meta-object system");
这不是很好,但至少它告诉你需要使用 Q_DECLARE_METATYPE。然而,在Qt 6中,它可以很好地编译,可执行文件将打印QVariant(MyType, MyType with i = 42),正如人们所期望的那样。不仅是QVariant,队列连接也可以在没有明确的Q_DECLARE_METATYPE的情况下工作。
现在,qRegisterMetaType呢?很不幸,这个还是需要的--假设你需要名称到类型的查找。虽然一个QMetaType对象知道它被构造出来的类型名称,但全局名称到元类型的映射只有在调用qRegisterMetaType之后才会发生。举例说明一下。
如果您使用旧样式的signal-slot-connections或使用,仍然需要具有可用的类型映射名称QMetaObject::invokeMethod。
在编译时创建QMetaType的能力也允许我们将一个类的属性的元类型存储在它的QMetaObject中。这一改变主要是出于QML,这一改变给我们带来了更高的性能,并且希望未来能减少内存消耗。
. 不幸的是,这个变化对属性声明中使用的类型提出了新的要求。当moc看到它时,它的类型(或者如果它是一个指针/引用,指向的类型)需要完整。为了说明这个问题,请看下面的例子。
// example.h#includestruct S;class MyClass : public QObject{ Q_OBJECT Q_PROPERTY(S* m_s MEMBER m_s); S *m_s = nullptr; public: MyClass(QObject *parent = nullptr) : QObject(parent) {}};
在Qt 5中,这没有问题。但是,在Qt 6中,您可能会收到类似错误。
In file included from qt/qtbase/include/QtCore/qmetatype.h:1, from qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qobject.h:54, from qt/qtbase/include/QtCore/qobject.h:1, from qt/qtbase/include/QtCore/QObject:1, from example.h:1, from moc_example.cpp:10:qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h: In instantiation of 'struct QtPrivate::IsPointerToTypeDerivedFromQObject':qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h:1073:63: required from 'struct QtPrivate::QMetaTypeTypeFlags'qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h:2187:40: required from 'QtPrivate::QMetaTypeInterface QtPrivate::QMetaTypeForType::metaType'qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h:2309:16: required from 'constexpr QtPrivate::QMetaTypeInterface* QtPrivate::qTryMetaTypeInterfaceForType() [with Unique = qt_meta_stringdata_MyClass_t; TypeCompletePair = QtPrivate::TypeAndForceComplete<s*, std::integral_constant >]'qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h:2328:55: required from 'QtPrivate::QMetaTypeInterface* const qt_incomplete_metaTypeArray [1]<qt_meta_stringdata_myclass_t, qtprivate::typeandforcecomplete<s*,="" std::integral_constant> >'moc_example.cpp:102:1: required from hereqt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h:766:23: error: invalid application of 'sizeof' to incomplete type 'S' 766 | static_assert(sizeof(T), "Type argument of Q_PROPERTY or Q_DECLARE_METATYPE(T*) must be fully defined"); | ^~~~~~~~~make: *** [Makefile:882: moc_example.o] Error 1
注意静态断言,它告诉您必须完全定义类型。可以通过三种不同的方式解决此问题:
不需要正向声明类,只需要包含定义S的头文件即可。
由于包含额外的头会对构建时间产生负面影响,你可以使用Q_MOC_INCLUDE宏来代替。那么只有moc会看到这个包含。简单地使用Q_MOC_INCLUDE("myheader.h")代替#include "myheader.h"。
或者你也可以在你的cpp文件中包含moc生成的文件。当然,这需要实际包含所需的头文件。
最后,在极少数情况下,您会故意使用不透明的指针。在这种情况下,您需要使用Q_DECLARE_OPAQUE_POINTER被使用。
尽管在我们的经验中具有不完整类型的属性并不常见,但这肯定不是最佳选择。此外,我们目前正在研究扩展工具支持,以至少自动检测到此问题。
同样,我们也尝试为元对象系统已知的方法(信号、槽和Q_INVOKABLE函数)的返回类型和参数创建元类型。这样做的好处是可以避免在基于字符串的连接和QML引擎内部进行一些名称到类型的查找。然而,我们知道,在methdos中,不完整的类型是非常常见的。因此,对于方法,我们仍然有一个回退路径,方法类型不需要完整,所以不需要在那里进行修改。如果可以的话,我们会在编译时将元类型存储在元对象中,但如果不能的话,我们会在运行时简单的查找。不过有一个例外:如果你使用声明式类型注册宏(QML_ELEMENT和friends)来注册你的类,我们甚至要求方法类型是完整的。在这种情况下,我们假设你公开的所有元方法实际上都是要在QML中使用的,因此你希望避免任何额外的运行时类型查找(注意这不会影响父类的元方法)。
QMetaType为QVariant提供动力
在我们重构了QMetaType之后,我们也可以清理我们古老的QVariant类的内部结构。在 Qt 6 之前,QVariant 在内部区分了用户类型和内置 Qt 类型,这使得该类变得非常复杂。QVariant也只能在其内部缓冲区中存储最大尺寸为sizeof(void *)和sizeof(double)的值。其他任何值都会被堆分配。在Qt 6中,其他任何东西都会包括常用的类,比如QString(因为QString在Qt 6中是3*sizeof(void *)大)。所以很明显,我们必须为Qt 6重新设计QVariant。而我们也确实重新设计了它!我们设法简化了它的内部架构。我们设法简化了它的内部架构,并使常见的用例变得更快。这包括修改 QVariant,使其现在在 SSO 缓冲区中存储类型 <= 3*sizeof(void *) 。除了允许继续存储QStrings而不需要额外的分配,这也使得它可以存储多态的PIMPL'd类型,如QImage3的QVariant中。这应该证明对在data()中返回图像的项目模型有利。
我们还在 QVariant 的现有方法中引入了一些行为变化。我们意识到沉默的行为改变是常见的bug来源,但认为当前的行为有足够的bug倾向,所以才会有这样的改变。以下是更改的内容列表。
QVariant 曾经将 isNull() 调用转发到它所包含的类型--但只适用于有限的 Qt 自己的类型集。这一点已经被改变了,isNull()现在只在QVariant为空或包含一个nullptr时返回true。
QVariant 的 operator== 现在使用 QMetaType::equals 进行比较。这意味着一些图形类型的行为改变,比如 QPixmap、QImage 和 QIcon,在 Qt 6 中永远不会进行等价比较(因为它们没有比较运算符)。此外,QVariant 中的浮点数现在不再通过 qFuzzyCompare 进行比较,而是使用精确比较。
另一个值得注意的变化是,我们删除了带有QDataStream的QVariant的构造函数。与其构建包含QDataStream的QVariant(与其他构造函数一致),不如尝试从数据流加载QVariant。如果您确实想要这种行为,请operator>>改用。还请注意,QVariant::Type在Qt 6中已弃用了它及其相关方法(但仍然存在)。QMetaType::Type已添加使用的替代API 。这很有用,因为QVariant::type()只能返回QVariant::UserType用户类型,而新的QVariant::typeId()总是返回具体的元类型。QVariant::userType这样做(在Qt 5中已经这样做),但是从其名称来看,它显然也不适用于内置类型。
最后,我们向QVariant添加了一些新功能:
QVariant::compare(const Variant &lhs, const QVariant &rhs)可用于比较两个变体。它返回一个std::optional。如果值不可比(因为类型不同,或者因为类型本身不具有可比性),std::nullopt则返回。否则,返回包含int的可选。如果所包含的值in中的值lhs小于,则为负数rhs;如果相等,则为0;否则为正数。
现在可以从QMetaType构造一个空的QVariant(而不是传入QMetaType :: Type,然后将其用于构造QMetaType)。由于类似的原因,可以将QMetaType传递给该convert函数。
由于QMetaType在Qt 6中存储对齐信息,因此QVariant现在支持存储超对齐类型。
结论与展望
Qt元类型系统的内部是Qt的一部分,大多数用户很少与之交互。但是,它是框架的核心,用于实现更多以用户为中心的部分,例如QML,QVariant,QtDbus,Qt Remote Objects和ActiveQt。借助Qt 6中的更新,我们希望它在下一个十年中能够像上一个一样为我们服务。
说到下一个十年,您可能想知道元类型系统的未来将如何发展。除了我们已经提到的使用它来增强QML引擎的计划之外,我们还打算改善信号/插槽连接逻辑。这些更改都不应该以任何方式影响您的代码,而只是在几个地方提高性能和内存使用率。在更远的将来,我们当然也将监视C ++的发展,尤其是在静态反射和元类方面。尽管我们预计moc不会很快消失,但我们确实考虑在它们广泛可用后,将其某些功能替换为C ++功能。
提前预告一下,我们在Qt 6.0中又增加了一项新功能:QMetaContainer。在下一篇博文中我们将会告诉你它是什么有什么作用。
感谢您的阅读,希望这篇文章能带给你一定的帮助!如果这篇文章没能满足你的需求可以前往慧都网获取更多相关教程。
您可能知道,Qt有一个元类型系统,该系统提供有关类型的运行时动态信息。它可以将您的类型存储在QVariant中,并在信号插槽系统中排成队列,并在整个QML引擎中使用。在即将发布的Qt 6.0版本中,我们借此机会重新审视了它的基础知识,并利用了C ++ 17为我们提供的功能。在下文中,我们将检查这些更改,并说明它们如何影响您的项目。
QMetaType更加了解您的类
在Qt 5中,QMetaType包含默认构造一个类,复制它并销毁它所必需的信息。此外,它知道如何将其保存到QDataStream以及从QDataStream加载它,并存储了一些标志来描述它的各种属性(例如,类型是否琐碎,枚举等)。另外,它将存储该类型的QMetaObject(如果有的话)和一个数字ID,以标识该类型以及类型名称。
最后,QMetaType包含用于比较某种(元)类型的对象,进行打印qDebug以及从一种类型转换为另一种类型的功能。但是,您必须使用QMetaType::registerComparators()QMetaType中的和其他静态寄存器函数才能真正利用该功能。这会将指向这些函数的指针放入相应的注册表中,基本上是从元类型ID到函数指针的映射。
在Qt 6中,我们做的第一件事就是扩展QMetaType中存储的信息。现代C++已经有将近10年的历史了,所以是时候在QMetaType中存储移动构造函数的信息了。而且为了更好地支持过度对齐的类型,我们现在也存储了你的类型的对齐要求。此外,我们认为注册表有点笨拙。毕竟,我们为什么要要求你调用QMetaType::registerEqualsComparator(),而我们已经可以通过简单地查看类型来知道这一点?所以在 Qt 6 中,QMetaType::registerEqualsComparator、QMetaType::registerComparators、qRegisterMetaTypeStreamOperators 和 QMetaType::registerDebugStreamOperator 已经被删除。元类型系统会自动知道这些。这里的例外是QMetaType::registerConverterFunction。相反,元类型系统将自动知道这些信息。这里的离群值是
QMetaType::registerEqualsComparatorQMetaType::registerComparatorsqRegisterMetaTypeStreamOperatorsQMetaType::registerDebugStreamOperatorQMetaType::registerConverterFunction。
由于无法可靠地知道应该使用哪些函数进行转换,并且我们允许注册基本上任意的转换,因此该功能与Qt 5中的相同。
通过这些更改,我们还可以统一处理Qt内部类型和用户注册的类型:这意味着例如QMetaType::compare现在可以使用int:
#include#includeint main() { int i = 1; int j = 2; int result = 0; const bool ok = QMetaType::compare(&i, &j, QMetaType::Int, &result); if (ok) { // prints -1 as expected in Qt 6 qDebug() << result; } else { // This would get printed in Qt 5 qDebug() << "Cannot compare integer with QMetaType :-("; } }
QMetaType在编译时知道您的类型
多亏了C++反思能力的各种进步,我们现在可以在编译时从一个类型中获得我们需要的所有信息--包括它的名字。在 Qt 中,我们使用了一个非常类似的方法,尽管对旧编译器进行了某些扩展和变通。但比实现更有趣的是它对你意味着什么。首先,我们不需要通过以下两种方式创建 QMetaType
QMetaType oldWay1 = QMetaType::fromName("KnownTypeName");
或者
QMetaType oldWay2(knownTypeID);
现在建议您使用以下命令创建QMetaType
QMetaType newWay = QMetaType::fromType();
如果你知道类型。其他方法仍然存在,当你在编译时不知道类型时,这些方法是有用的。然而,fromType 避免了在运行时从 id/name 到 QMetaType 的一次查找。请注意,从 Qt 5.15 开始,你已经可以使用 fromType 了,但它仍然会进行一次查找。此外,你不能复制QMetaType,这限制了它的实用性,使它更方便地传递类型id。然而,在 Qt 6 中,QMetaType 是可以复制的。
你现在可能会问,这对 Q_DECLARE_METATYPE 和 qRegisterMetaType 意味着什么。毕竟,如果我们可以在编译时创建QMetaTypes,我们真的需要它们吗?
我们先来看一个例子。
#include#include#includestruct MyType { int i = 42; friend QDebug operator<<(QDebug dbg, MyType t) { QDebugStateSaver saver(dbg); dbg.nospace() << "MyType with i = " << t.i; return dbg; } }; int main() { MyType myInstance; QVariant var = QVariant::fromValue(myInstance); qDebug() << var; }
在Qt 5中,这将导致以下带有gcc的错误消息(+有关实例化失败的更多警告):
/usr/include/qt/QtCore/qmetatype.h: In instantiation of 'constexprint qMetaTypeId() [with T = MyType]':/usr/include/qt/QtCore/qvariant.h:371:37: required from 'static QVariantQVariant::fromValue(const T&) [with T = MyType]'test.cpp:16:48: required from here/usr/include/qt/QtCore/qglobal.h:121:63: error: static assertion failed: Type isnot registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt'smeta-object system 121 | # define Q_STATIC_ASSERT_X(Condition, Message) static_assert(bool(Condition), Message) |^~~~~~~~~~~~~~~/usr/include/qt/QtCore/qmetatype.h:1916:5: note: in expansion of macro 'Q_STATIC_ASSERT_X' 1916 | Q_STATIC_ASSERT_X(QMetaTypeId2::Defined, "Type is not registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt's meta-object system");
这不是很好,但至少它告诉你需要使用 Q_DECLARE_METATYPE。然而,在Qt 6中,它可以很好地编译,可执行文件将打印QVariant(MyType, MyType with i = 42),正如人们所期望的那样。不仅是QVariant,队列连接也可以在没有明确的Q_DECLARE_METATYPE的情况下工作。
现在,qRegisterMetaType呢?很不幸,这个还是需要的--假设你需要名称到类型的查找。虽然一个QMetaType对象知道它被构造出来的类型名称,但全局名称到元类型的映射只有在调用qRegisterMetaType之后才会发生。举例说明一下。
如果您使用旧样式的signal-slot-connections或使用,仍然需要具有可用的类型映射名称QMetaObject::invokeMethod。
在编译时创建QMetaType的能力也允许我们将一个类的属性的元类型存储在它的QMetaObject中。这一改变主要是出于QML,这一改变给我们带来了更高的性能,并且希望未来能减少内存消耗。
. 不幸的是,这个变化对属性声明中使用的类型提出了新的要求。当moc看到它时,它的类型(或者如果它是一个指针/引用,指向的类型)需要完整。为了说明这个问题,请看下面的例子。
// example.h#includestruct S;class MyClass : public QObject{ Q_OBJECT Q_PROPERTY(S* m_s MEMBER m_s); S *m_s = nullptr; public: MyClass(QObject *parent = nullptr) : QObject(parent) {}};
在Qt 5中,这没有问题。但是,在Qt 6中,您可能会收到类似错误。
In file included from qt/qtbase/include/QtCore/qmetatype.h:1, from qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qobject.h:54, from qt/qtbase/include/QtCore/qobject.h:1, from qt/qtbase/include/QtCore/QObject:1, from example.h:1, from moc_example.cpp:10:qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h: In instantiation of 'struct QtPrivate::IsPointerToTypeDerivedFromQObject':qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h:1073:63: required from 'struct QtPrivate::QMetaTypeTypeFlags'qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h:2187:40: required from 'QtPrivate::QMetaTypeInterface QtPrivate::QMetaTypeForType::metaType'qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h:2309:16: required from 'constexpr QtPrivate::QMetaTypeInterface* QtPrivate::qTryMetaTypeInterfaceForType() [with Unique = qt_meta_stringdata_MyClass_t; TypeCompletePair = QtPrivate::TypeAndForceComplete<s*, std::integral_constant >]'qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h:2328:55: required from 'QtPrivate::QMetaTypeInterface* const qt_incomplete_metaTypeArray [1]<qt_meta_stringdata_myclass_t, qtprivate::typeandforcecomplete<s*,="" std::integral_constant> >'moc_example.cpp:102:1: required from hereqt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h:766:23: error: invalid application of 'sizeof' to incomplete type 'S' 766 | static_assert(sizeof(T), "Type argument of Q_PROPERTY or Q_DECLARE_METATYPE(T*) must be fully defined"); | ^~~~~~~~~make: *** [Makefile:882: moc_example.o] Error 1
注意静态断言,它告诉您必须完全定义类型。可以通过三种不同的方式解决此问题:
不需要正向声明类,只需要包含定义S的头文件即可。
由于包含额外的头会对构建时间产生负面影响,你可以使用Q_MOC_INCLUDE宏来代替。那么只有moc会看到这个包含。简单地使用Q_MOC_INCLUDE("myheader.h")代替#include "myheader.h"。
或者你也可以在你的cpp文件中包含moc生成的文件。当然,这需要实际包含所需的头文件。
最后,在极少数情况下,您会故意使用不透明的指针。在这种情况下,您需要使用Q_DECLARE_OPAQUE_POINTER被使用。
尽管在我们的经验中具有不完整类型的属性并不常见,但这肯定不是最佳选择。此外,我们目前正在研究扩展工具支持,以至少自动检测到此问题。
同样,我们也尝试为元对象系统已知的方法(信号、槽和Q_INVOKABLE函数)的返回类型和参数创建元类型。这样做的好处是可以避免在基于字符串的连接和QML引擎内部进行一些名称到类型的查找。然而,我们知道,在methdos中,不完整的类型是非常常见的。因此,对于方法,我们仍然有一个回退路径,方法类型不需要完整,所以不需要在那里进行修改。如果可以的话,我们会在编译时将元类型存储在元对象中,但如果不能的话,我们会在运行时简单的查找。不过有一个例外:如果你使用声明式类型注册宏(QML_ELEMENT和friends)来注册你的类,我们甚至要求方法类型是完整的。在这种情况下,我们假设你公开的所有元方法实际上都是要在QML中使用的,因此你希望避免任何额外的运行时类型查找(注意这不会影响父类的元方法)。
QMetaType为QVariant提供动力
在我们重构了QMetaType之后,我们也可以清理我们古老的QVariant类的内部结构。在 Qt 6 之前,QVariant 在内部区分了用户类型和内置 Qt 类型,这使得该类变得非常复杂。QVariant也只能在其内部缓冲区中存储最大尺寸为sizeof(void *)和sizeof(double)的值。其他任何值都会被堆分配。在Qt 6中,其他任何东西都会包括常用的类,比如QString(因为QString在Qt 6中是3*sizeof(void *)大)。所以很明显,我们必须为Qt 6重新设计QVariant。而我们也确实重新设计了它!我们设法简化了它的内部架构。我们设法简化了它的内部架构,并使常见的用例变得更快。这包括修改 QVariant,使其现在在 SSO 缓冲区中存储类型 <= 3*sizeof(void *) 。除了允许继续存储QStrings而不需要额外的分配,这也使得它可以存储多态的PIMPL'd类型,如QImage3的QVariant中。这应该证明对在data()中返回图像的项目模型有利。
我们还在 QVariant 的现有方法中引入了一些行为变化。我们意识到沉默的行为改变是常见的bug来源,但认为当前的行为有足够的bug倾向,所以才会有这样的改变。以下是更改的内容列表。
QVariant 曾经将 isNull() 调用转发到它所包含的类型--但只适用于有限的 Qt 自己的类型集。这一点已经被改变了,isNull()现在只在QVariant为空或包含一个nullptr时返回true。
QVariant 的 operator== 现在使用 QMetaType::equals 进行比较。这意味着一些图形类型的行为改变,比如 QPixmap、QImage 和 QIcon,在 Qt 6 中永远不会进行等价比较(因为它们没有比较运算符)。此外,QVariant 中的浮点数现在不再通过 qFuzzyCompare 进行比较,而是使用精确比较。
另一个值得注意的变化是,我们删除了带有QDataStream的QVariant的构造函数。与其构建包含QDataStream的QVariant(与其他构造函数一致),不如尝试从数据流加载QVariant。如果您确实想要这种行为,请operator>>改用。还请注意,QVariant::Type在Qt 6中已弃用了它及其相关方法(但仍然存在)。QMetaType::Type已添加使用的替代API 。这很有用,因为QVariant::type()只能返回QVariant::UserType用户类型,而新的QVariant::typeId()总是返回具体的元类型。QVariant::userType这样做(在Qt 5中已经这样做),但是从其名称来看,它显然也不适用于内置类型。
最后,我们向QVariant添加了一些新功能:
QVariant::compare(const Variant &lhs, const QVariant &rhs)可用于比较两个变体。它返回一个std::optional。如果值不可比(因为类型不同,或者因为类型本身不具有可比性),std::nullopt则返回。否则,返回包含int的可选。如果所包含的值in中的值lhs小于,则为负数rhs;如果相等,则为0;否则为正数。
现在可以从QMetaType构造一个空的QVariant(而不是传入QMetaType :: Type,然后将其用于构造QMetaType)。由于类似的原因,可以将QMetaType传递给该convert函数。
由于QMetaType在Qt 6中存储对齐信息,因此QVariant现在支持存储超对齐类型。
结论与展望
Qt元类型系统的内部是Qt的一部分,大多数用户很少与之交互。但是,它是框架的核心,用于实现更多以用户为中心的部分,例如QML,QVariant,QtDbus,Qt Remote Objects和ActiveQt。借助Qt 6中的更新,我们希望它在下一个十年中能够像上一个一样为我们服务。
说到下一个十年,您可能想知道元类型系统的未来将如何发展。除了我们已经提到的使用它来增强QML引擎的计划之外,我们还打算改善信号/插槽连接逻辑。这些更改都不应该以任何方式影响您的代码,而只是在几个地方提高性能和内存使用率。在更远的将来,我们当然也将监视C ++的发展,尤其是在静态反射和元类方面。尽管我们预计moc不会很快消失,但我们确实考虑在它们广泛可用后,将其某些功能替换为C ++功能。
提前预告一下,我们在Qt 6.0中又增加了一项新功能:QMetaContainer。在下一篇博文中我们将会告诉你它是什么有什么作用。
感谢您的阅读,希望这篇文章能带给你一定的帮助!如果这篇文章没能满足你的需求可以前往慧都网获取更多相关教程。