来源: https://woboq.com/blog/qmetatype-knows-your-types.html
QMetaType
是Qt
用来获取类型动态运行信息的工具。通过它能够使诸如QVariant
的类能够封装自定义类型,复制信号队列中的参数等事情。
如果你想知道Q_DECLARE_META_TYPE
和qRegisterMetaType
是做什么的,该什么时候使用,可以读一下。这篇文章会告知关于QMetaType
的相关信息:为什么需要这么一个类,什么时候使用它,它是如何工作的。
首先需要回顾一下Qt
的历史。QMetaType
在Qt4.0
被引入。创建这样一个类是为了解决异步的信号机制(Qt::QueuedConnection)
。为了使队列中等待调用的槽函数能够正常工作,信号中参数会被复制和存储起来,以便事件循环函数能够正确调用。
为了能够正常调用队列中的槽函数,信号中的参数会被复制,存储在稍后处理的事件中。同时,当完成槽函数的调用后,还需要删除这些副本。但是使用Qt::DirectConnection
参数的连接就不需要了,槽函数可以直接使用栈上的参数。
在QMetaObject::activate
中用于分发信号的代码有一个指针数组指向信号参数。但是Qt
知道的参数类型都是moc
所导出的名称字符串。
QMetaType
提供了一种方式能够直接从字符串,例如"QPoint"
,去获取副本和销毁对象的方法。Qt
能够使用void *QMetaType::create(int type, void *copy)
和QMetaType::destroy(int type, void *data)
来拷贝和销毁参数。对于int
类型,则可以使用QMetaType::type(const char *typeName)
来获取,这些都是moc
所提供的能力。QMetaType
还提供了方法让开发者能够注册任意类型到元对象数据库中。
另一个例子是QVariant
。在Qt3.x
版本中的QVariant
仅支持内置类型,因为QVariant
所包含的类型需要跟QVariant
一起被拷贝和销毁。但是有QMetaType
后,QVariant
能够封装任意已注册的类型,因为QVariant
能够跟拷贝和销毁这些对象实例。
Qt4.0
之后很多东西发生了改变。QtScript
和QML
加强了对动态类型集成的使用,所以需要额外的进行很多优化。
下面是每个类型在元对象系统中会持有的信息:
Qt4.7
之后,能够被相同的类型注册不同的名字,这个特性对于typedef
非常有用。flags
用于指定一些跟QTypeInfo
相同的信息或转换方式QMetaType::registerConverter
设置QObject
数据。此外,如果存在类型信息的话,也会保存其中QTypeInfo
QTypeInfo
是一个与QMetaType
相互正交的trait class,它允许开发者通过Q_DECLARE_TYPEINFO
手动指定类型是否可以借助memmove
移动或类型的构造函数 /析构函数是否能够运行。QTypeInfo
主要用于优化容器的存储。
例如,隐式分享类可以通过memmove
移动。而普通的拷贝则需要在拷贝构造函数中增加引用计数,在析构函数用减少引用计数。
C++11
引入移动构造函数和标准traits
类型来解决这个问题,但是QTypeInfo
是在C++11
之前设计出来的,并且需要兼容一些旧版本的编译器,所以只好将就使用。
因为一些历史原因,对于内置类型和自定义类型有不同的处理方式。对于QtCore
中的内置类型,每个meta-type
函数都指定了特定的函数进行处理。不过在Qt5.0
使用模板进行了重构。但让我们感兴趣的是对自定义类型处方式。
QVector<QCustomTypeInfo>
这个类含有相关信息和一系列函数指针。
Q_DECLARE_METATYPE
宏这个宏针对于特殊的类型特化了模板QMetaTypeId
。实际上,它特化了模板类QMetaTypeId2
和大多数QMetaTypeId2
用到的函数。这里并不清楚这背后的原因。也许这样Qt
可以添加更多的内置类型而不会破坏以前使用Q_DECLARE_METATYPE
的代码。
QMetaTypeId
通过调用qMetaType<T>()
中的QMetaTypeId::qt_metatype_id
在编译时指定类型所对应的元类型id。第一次调用的时候,这个函数会调用QMetaType
的内部函数来给自定义类型注册和分配元类型id,同时使用调用这个宏时所填写的名称。这些信息会被存储在一个static
变量中。
除了类型名称,其他的信息会通过模板在编译自动推导。
qRegisterMetaType
使用Q_DECLARE_METATYPE
注册的类型将在首次使用qMetaTypeId()
时进行实际注册并分配一个id。使用QVariant
封装一个类型就是典型的例子。但是在连接信号和槽函数的时候并不会注册,这需要你使用qRegisterMetaType
进行手动注册。
开发者经常在看到编译错误或者运行错误的是才想起来他们忘了注册自定义类型。如果不需要这一步骤,那不是很好吗?其实,Q_DECLARE_METATYPE
存在的唯一原因就是需要来获取类型的名称。但是在某些情况下,我们并不需要这个宏也可以在运行时获取到类型名称。例如,对于QList<T>
,如果T
是已经注册的类型,我们可以在类型系统里检索到,也可以使用"QList<" + QMetaType::name(qMetaTypeId<T>()) + ">"
来进行构建。我们可以在一系列模板类上做这种操作,例如QList, QVector, QSharedPointer, QPointer, QMap, QHash...
。我们甚至可以在moc
的帮助下,直接定义指向QObject
子类的指针,使用T::staticMetaObject.className() + "*"
即可。在Qt5.5
,还能自动声明Q_GADGET
和Q_ENUM
。
这就是Q_DECLARE_METATYPE
所作的工作,但是要在Q_PROPERTY
或者信号参数中使用自定义类型,还是需要使用qRegisterMetaType
注册。但是从Qt5.x
开始,如果moc
可以确定该类型可以注册为元类型,则moc
生成的代码将自动调用qRegisterMetaType
。
在Qt5.0
之前,我尝试过能够在不需要名称的情况下摆脱Q_DECLARE_METATYPE
的使用,大概的做法如下:
template<typename T> QMetaTypeId {
static int qt_metatype_id() {
static int typeId = QMetaType::registerMetaType(/*...*/);
return typeId;
}
};
按照C++
的标准实现,对于每个类型都会有一个对应的实例来生成QMetaTypeId::qt_metatype_id()::typeId
。但是实际上一些编译器和链接器并不遵守这些规则。特别的,在Windows
上,即便使用了导出宏,对于每个库还是只会生成一份实例。因此我们始终需要一个我们没有的名称标识符,所有在Qt5
上,还是需要通过注册类型的方式来获取名称。
1
wzzzx OP 哈哈,有翻译的不好的地方请多多指教~
|
2
QBugHunter 2020-10-06 15:52:14 +08:00 via Android
马克一下,话说 qt6 快出了吧😄
|