翻译|使用教程|编辑:鲍佳佳|2020-10-26 13:39:08.137|阅读 494 次
概述:您可能知道,Qt有一个元类型系统,该系统提供有关类型的运行时动态信息。它可以将您的类型存储在QVariant中,并在信号插槽系统中排成队列,并在整个QML引擎中使用。在即将发布的Qt 6.0版本中,我们借此机会重新审视了它的基础知识,并利用了C ++ 17为我们提供的功能。在下文中,我们将检查这些更改,并说明它们如何影响您的项目。
# 慧都年终大促·界面/图表报表/文档/IDE等千款热门软控件火热促销中 >>
相关链接:
Qt是一个跨平台框架,通常用作图形工具包,它不仅创建CLI应用程序中非常有用。而且它也可以在三种主要的台式机操作系统以及移动操作系统(如Symbian,Nokia Belle,Meego Harmattan,MeeGo或BB10)以及嵌入式设备,Android(Necessitas)和iOS的端口上运行。现在我们为你提供了免费的试用版。赶快点击下载Qt最新试用版吧>>
慧都现推出“软件国产化服务季”(点击查看详情),Qt正版授权获取低价优惠>>
您可能知道,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#include int 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 #include struct 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 'constexpr int qMetaTypeId() [with T = MyType]': /usr/include/qt/QtCore/qvariant.h:371:37: required from 'static QVariant QVariant::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 is not registered, please use the Q_DECLARE_METATYPE macro to make it known to Qt's meta-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之后才会发生。举例说明一下。
struct Custom {};
const auto myMetaType = QMetaType::fromType();
// At this point, we do not know that the name "Custom" maps to the type Custom
int id = QMetaType::type("Custom"); Q_ASSERT(id == QMetaType::UnknownType);
qRegisterMetaType(); // from now on, the name -> type mapping works, too id = QMetaType::type("Custom") Q_ASSERT(id == myMetaType.id());
如果您使用旧样式的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>]' qt/qtbase/include/QtCore/../../../../qtdev/qtbase/src/corelib/kernel/qmetatype.h:2328:55: required from 'QtPrivate::QMetaTypeInterface* const qt_incomplete_metaTypeArray [1]> >' moc_example.cpp:102:1: required from here qt/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
注意静态断言,它告诉您必须完全定义类型。可以通过三种不同的方式解决此问题:
最后,在极少数情况下,您会故意使用不透明的指针。在这种情况下,您需要使用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倾向,所以才会有这样的改变。以下是更改的内容列表。
另一个值得注意的变化是,我们删除了带有QDataStream的QVariant的构造函数。与其构建包含QDataStream的QVariant(与其他构造函数一致),不如尝试从数据流加载QVariant。如果您确实想要这种行为,请operator>>改用。还请注意,QVariant::Type在Qt 6中已弃用了它及其相关方法(但仍然存在)。QMetaType::Type已添加使用的替代API 。这很有用,因为QVariant::type()只能返回QVariant::UserType用户类型,而新的QVariant::typeId()总是返回具体的元类型。QVariant::userType这样做(在Qt 5中已经这样做),但是从其名称来看,它显然也不适用于内置类型。
最后,我们向QVariant添加了一些新功能:
结论与展望
Qt元类型系统的内部是Qt的一部分,大多数用户很少与之交互。但是,它是框架的核心,用于实现更多以用户为中心的部分,例如QML,QVariant,QtDbus,Qt Remote Objects和ActiveQt。借助Qt 6中的更新,我们希望它在下一个十年中能够像上一个一样为我们服务。
说到下一个十年,您可能想知道元类型系统的未来将如何发展。除了我们已经提到的使用它来增强QML引擎的计划之外,我们还打算改善信号/插槽连接逻辑。这些更改都不应该以任何方式影响您的代码,而只是在几个地方提高性能和内存使用率。在更远的将来,我们当然也将监视C ++的发展,尤其是在静态反射和元类方面。尽管我们预计moc不会很快消失,但我们确实考虑在它们广泛可用后,将其某些功能替换为C ++功能。
提前预告一下,我们在Qt 6.0中又增加了一项新功能:QMetaContainer。在下一篇博文中我们将会告诉你它是什么有什么作用。
感谢您的阅读,希望这篇文章能带给你一定的帮助!如果这篇文章没能满足你的需求、点击获取更多文章教程!现在立刻下载Qt6免费试用吧!更多Qt类开发工具QtitanRibbon、QtitanChart、QtitanNavigation、QtitanDocking、QtitanDataGrid在线订购现直降1000元,欢迎咨询慧都获取更多优惠>>
本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@pclwef.cn
文章转载自: