彩票走势图

Qt6最新资讯:QMetaType + QVariant的新增功能

翻译|使用教程|编辑:鲍佳佳|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
#include 
struct 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

注意静态断言,它告诉您必须完全定义类型。可以通过三种不同的方式解决此问题:

  1. 不需要正向声明类,只需要包含定义S的头文件即可。
  2. 由于包含额外的头会对构建时间产生负面影响,你可以使用Q_MOC_INCLUDE宏来代替。那么只有moc会看到这个包含。简单地使用Q_MOC_INCLUDE("myheader.h")代替#include "myheader.h"。
  3. 或者你也可以在你的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。在下一篇博文中我们将会告诉你它是什么有什么作用。

感谢您的阅读,希望这篇文章能带给你一定的帮助!如果这篇文章没能满足你的需求、点击获取更多文章教程!现在立刻下载Qt6免费试用吧!更多Qt类开发工具QtitanRibbonQtitanChartQtitanNavigationQtitanDockingQtitanDataGrid在线订购现直降1000元,欢迎咨询慧都获取更多优惠>>


标签:

本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@cahobeh.cn

文章转载自:

为你推荐

  • 推荐视频
  • 推荐活动
  • 推荐产品
  • 推荐文章
  • 慧都慧问
扫码咨询


添加微信 立即咨询

电话咨询

客服热线
023-68661681

TOP