QT 中的元对象系统(八):数据类型的识别 - 详解
目录
2.1.Q_DECLARE_METATYPE + qRegisterMetaType() (最常用、最核心)
2.2.仅使用 Q_DECLARE_METATYPE (有限场景)
2.3.继承 QObject 并使用 Q_OBJECT 宏 (适用于 QObject 派生类)
5.函数qRegisterNormalizedMetaType实现分析
7.qRegisterMetaType禁止/不建议注册的类型
1.背景
Qt 的元类型系统(Meta-Type System)是整个框架的底层基础设施之一,它贯穿于 Qt 的多个核心机制中。简单来说,它的核心作用是在运行时提供类型信息,从而实现了 C++ 本身不直接支持的一些动态特性。
主要表现在以下几个方面:
1.信号与槽机制 (Signals & Slots)
这是元类型系统最广为人知的用途。
- 类型安全的跨对象通信:当你连接一个信号到一个槽时,Qt 需要确保信号的参数类型与槽的参数类型是兼容的。元类型系统就是用来在运行时检查这种兼容性的。
- 自定义类型的传递:要在信号和槽之间传递自定义类型(例如,你自己写的
Student类),该类型必须被元类型系统所知。这就是为什么需要Q_DECLARE_METATYPE宏和qRegisterMetaType()函数。元类型系统会为这些类型生成必要的代码,以便 Qt 可以在信号发射时序列化参数,并在槽函数调用时反序列化它们。 queued连接的实现:在不同线程间进行queued连接时,信号的参数需要被安全地从一个线程传递到另一个线程。元类型系统负责将参数打包(marshalling)成可以跨线程安全传输的数据,然后在目标线程中解包(unmarshalling)。
2.QVariant 类型
QVariant 是一个可以存储各种类型值的万能容器。它的实现完全依赖于元类型系统。
- 类型存储与检索:当你调用
QVariant::setValue<T>()时,QVariant会使用qMetaTypeId<T>()来获取类型T的 ID,并将其与实际的值一起存储。 - 类型判断:
QVariant::typeId()方法返回的就是该变体中存储的值的元类型 ID。你可以通过比较这个 ID 来判断QVariant存储的是哪种类型。 - 值的提取:
QVariant::value<T>()方法会检查存储的元类型 ID 是否与T的元类型 ID 匹配,如果匹配,就安全地将值转换为类型T并返回。
3.动态属性系统 (Dynamic Properties)
Qt 允许你在运行时为任何 QObject 派生类的实例添加、修改和删除属性,这超出了 C++ 编译器所能提供的静态反射能力。
- 属性类型管理:当你使用
QObject::setProperty(const char *name, const QVariant &value)时,QVariant内部的元类型信息会告诉 Qt 这个新属性的类型。 - 属性值的获取:
QObject::property(const char *name)返回一个QVariant,你需要利用元类型系统来判断其类型并提取值。
4.序列化与反序列化
Qt 提供了强大的序列化机制,用于将对象保存到文件或通过网络传输。
QDataStream操作:QDataStream可以序列化和反序列化基本数据类型以及许多 Qt 内置类型。对于自定义类型,你需要为其实现operator<<和operator>>。元类型系统在这里的作用是,它允许QDataStream(通过QVariant)处理那些在编译时无法预知的类型。QSettings:QSettings用于存储应用程序的配置。当你存储一个自定义类型时,它会被转换为QVariant,而QVariant的存储和读取则依赖于元类型系统。
5.元对象反射 (Meta-Object Reflection)
QObject 的元对象 (QMetaObject) 包含了该类的完整运行时类型信息。
- 类信息查询:你可以通过元对象获取类名、父类信息、方法、信号、槽和属性的列表。
- 动态调用:
QMetaObject::invokeMethod()允许你在运行时通过名字来调用一个对象的方法(信号或槽)。元类型系统确保了传递给invokeMethod的QVariant参数与方法的实际参数类型是兼容的。
可以说,元类型系统是 Qt 实现其 “动态” 特性的基石。它弥补了静态类型语言 C++ 在运行时类型信息处理上的不足,使得信号与槽、QVariant、动态属性等核心功能成为可能。
2.Qt系统识别到数据类型的方法
2.1.Q_DECLARE_METATYPE + qRegisterMetaType() (最常用、最核心)
这是最标准、最完整的方法,适用于绝大多数场景,尤其是当你需要在信号槽中传递自定义类型,或者将其存储在 QVariant 中时。
1.声明元类型 (Q_DECLARE_METATYPE)
在你的自定义类型定义之后,使用 Q_DECLARE_METATYPE 宏来告诉 Qt 元类型系统:“这个类型是我的,请为它创建必要的元信息模板。”
位置:通常在定义类型的头文件中,紧跟在类或结构体定义之后。
示例:
// MyType.h
#ifndef MYTYPE_H
#define MYTYPE_H
#include
#include
struct MyType {
int id;
QString name;
};
// 声明 MyType 为元类型
Q_DECLARE_METATYPE(MyType)
#endif // MYTYPE_H
- 为
MyType生成QMetaTypeId<MyType>的模板特化。 - 这使得
qMetaTypeId<MyType>()函数能够编译通过并返回一个有效的类型 ID。 - 这是后续所有操作的基础。
2.注册元类型 (qRegisterMetaType())
仅仅声明是不够的,你还需要在运行时将类型的具体信息(如大小、构造函数、析构函数等)注册到 Qt 的全局元类型数据库中。
位置:通常在 main 函数的开头,QApplication 实例化之后。
示例:
// main.cpp
#include
#include
#include "MyType.h"
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
// 在运行时注册 MyType
qRegisterMetaType("MyType");
// ... 你的应用程序代码 ...
return a.exec();
}
- 为
MyType分配一个唯一的、非负的元类型 ID。 - 将
MyType的大小、构造和析构函数等信息存入一个全局列表。 - 使得 Qt 的信号槽机制、
QVariant等功能能够在运行时找到并操作MyType的实例。
注意:qRegisterMetaType 的模板参数 T 必须是可默认构造、可复制构造和可析构的。
2.2.仅使用 Q_DECLARE_METATYPE (有限场景)
在某些简单的场景下,你可能只需要将自定义类型存储在 QVariant 中,而不需要在信号槽中传递。在这种情况下,仅使用 Q_DECLARE_METATYPE 可能就足够了。
// main.cpp
#include
#include "MyType.h" // 已经包含了 Q_DECLARE_METATYPE(MyType)
int main() {
MyType t{1, "Hello"};
// 将 MyType 存储到 QVariant 中
QVariant var = QVariant::fromValue(t);
// 从 QVariant 中提取 MyType
if (var.canConvert()) {
MyType t2 = var.value();
// ...
}
return 0;
}
原因:QVariant::fromValue<T>() 会调用 qMetaTypeId<T>(),而 Q_DECLARE_METATYPE 已经为我们提供了这个函数的实现。在这个过程中,qMetaTypeId<T>() 内部会自动调用 qRegisterMetaType<T>() 来完成注册。
但是,为了代码的清晰和健壮性,特别是为了避免在更复杂的场景下(比如后续加入信号槽)出现难以预料的错误,强烈建议始终显式地调用 qRegisterMetaType()。
2.3.继承 QObject 并使用 Q_OBJECT 宏 (适用于 QObject 派生类)
如果你自定义的类型本身就是 QObject 的子类(例如一个自定义的窗口部件或一个数据模型),那么情况会简单一些。
// MyObject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H
#include
#include
class MyObject : public QObject {
Q_OBJECT // 关键宏
Q_PROPERTY(int id READ id WRITE setId)
Q_PROPERTY(QString name READ name WRITE setName)
public:
explicit MyObject(QObject *parent = nullptr) : QObject(parent) {}
int id() const { return m_id; }
void setId(int id) { m_id = id; }
QString name() const { return m_name; }
void setName(const QString &name) { m_name = name; }
private:
int m_id;
QString m_name;
};
#endif // MYOBJECT_H
Q_OBJECT宏会触发 Qt 的 MOC (Meta-Object Compiler) 工具,为MyObject生成大量元信息代码,包括一个staticMetaObject成员。- 继承自
QObject的类自动被元类型系统识别。你不需要再手动调用Q_DECLARE_METATYPE或qRegisterMetaType(尽管在某些复杂的信号槽场景中,注册指针类型MyObject*可能仍然是个好习惯)。 - 这种方法的强大之处在于提供了完整的反射能力,包括动态属性、信号槽、事件等。
总结与选择
| 场景 | 推荐方法 | 关键点 |
|---|---|---|
| 在信号槽中传递非 QObject 类型 | Q_DECLARE_METATYPE + qRegisterMetaType() | 必须且完整的步骤。 |
将类型存入 QVariant | Q_DECLARE_METATYPE | 通常已足够,但显式注册更佳。 |
你的类型是 QObject 子类 | 继承 QObject + Q_OBJECT 宏 | 自动注册,功能最强大。 |
传递 QObject 子类的指针 | qRegisterMetaType<MyObject*>("MyObject*") | 确保在跨线程信号槽中正确传递。 |
3.宏Q_DECLARE_METATYPE实现分析
在Qt5.12.12源码中qmetatype.h中可以找到它的实现:
#ifndef Q_MOC_RUN
#define Q_DECLARE_METATYPE(TYPE) Q_DECLARE_METATYPE_IMPL(TYPE)
#define Q_DECLARE_METATYPE_IMPL(TYPE) \
QT_BEGIN_NAMESPACE \
template <> \
struct QMetaTypeId< TYPE > \
{ \
enum { Defined = 1 }; \
static int qt_metatype_id() \
{ \
static QBasicAtomicInt metatype_id = Q_BASIC_ATOMIC_INITIALIZER(0); \
if (const int id = metatype_id.loadAcquire()) \
return id; \
const int newId = qRegisterMetaType< TYPE >(#TYPE, \
reinterpret_cast< TYPE *>(quintptr(-1))); \
metatype_id.storeRelease(newId); \
return newId; \
} \
}; \
QT_END_NAMESPACE
#endif // Q_MOC_RUN
在函数内部定义了一个静态的原子整数metatype_id,并初始化为 0,这是 Qt 提供的一个原子操作类,用于在多线程环境中安全地进行整数的读写操作,避免了数据竞争。
const int newId = qRegisterMetaType< TYPE >(#TYPE, reinterpret_cast< TYPE *>(quintptr(-1)));
qRegisterMetaType< TYPE >(#TYPE, ...):调用底层的注册函数。#TYPE:将类型名TYPE转换为字符串字面量,作为类型的名称。reinterpret_cast< TYPE *>(quintptr(-1)):这是一个非常巧妙的技巧。它传递了一个 “无效” 的指针(所有位都为 1)作为第二个参数。这个参数是一个类型示例指针,qRegisterMetaType有时会用它来推断类型信息,但在这个上下文中,它主要是为了帮助qRegisterMetaType区分不同的重载版本,或者作为一种 “占位符”。它并不指向一个实际的TYPE对象。
4.函数qMetaTypeId实现分析
qMetaTypeId 是一个模板函数,它的核心目的是获取一个具体类型 T 的元类型 ID。它的函数原型就是:
template
inline int qMetaTypeId();
这个函数的实现依赖于模板参数 T,它会去查找 QMetaTypeId<T> 这个模板结构体,并调用其静态成员函数 qt_metatype_id()。
template
inline Q_DECL_CONSTEXPR int qMetaTypeId()
{
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");
return QMetaTypeId2::qt_metatype_id();
}
QMetaTypeId2<T> 模板结构如下:
template
struct QMetaTypeId2
{
enum { Defined = QMetaTypeId::Defined, IsBuiltIn=false };
static inline Q_DECL_CONSTEXPR int qt_metatype_id() { return QMetaTypeId::qt_metatype_id(); }
};
template
struct QMetaTypeId2 : QMetaTypeId2 {};
template
struct QMetaTypeId2 { enum {Defined = false }; };
这些模板结构通过分层包装和特化处理,实现了对不同类型(包括原始类型、const 引用、非 const 引用)的元信息管理:
QMetaTypeId<T>:作为底层,提供类型T的基础元信息(依赖QMetaTypeIdQObject或特化实现)。QMetaTypeId2<T>:作为上层接口,包装QMetaTypeId<T>的信息,并通过特化处理引用类型(const T&复用原类型信息,T&直接标记为不支持)。- 最终,
qMetaTypeId<T>()函数通过QMetaTypeId2<T>获取类型 ID,确保只有合法类型(非引用或 const 引用)能被元系统识别。
最终转向了QMetaTypeId<T>::qt_metatype_id()函数处理:
template
struct QMetaTypeId : public QMetaTypeIdQObject
{
};
template ::Value ? QMetaType::PointerToQObject :
QtPrivate::IsGadgetHelper::IsRealGadget ? QMetaType::IsGadget :
QtPrivate::IsPointerToGadgetHelper::IsRealGadget ? QMetaType::PointerToGadget :
QtPrivate::IsQEnumHelper::Value ? QMetaType::IsEnumeration : 0>
struct QMetaTypeIdQObject
{
enum {
Defined = 0
};
};
template
struct QMetaTypeIdQObject
{
enum {
Defined = 1
};
static int qt_metatype_id()
{
static QBasicAtomicInt metatype_id = Q_BASIC_ATOMIC_INITIALIZER(0);
if (const int id = metatype_id.loadAcquire())
return id;
const char * const cName = T::staticMetaObject.className();
QByteArray typeName;
typeName.reserve(int(strlen(cName)) + 1);
typeName.append(cName).append('*');
const int newId = qRegisterNormalizedMetaType(
typeName,
reinterpret_cast(quintptr(-1)));
metatype_id.storeRelease(newId);
return newId;
}
};
template
struct QMetaTypeIdQObject
{
enum {
Defined = std::is_default_constructible::value
};
static int qt_metatype_id()
{
static QBasicAtomicInt metatype_id = Q_BASIC_ATOMIC_INITIALIZER(0);
if (const int id = metatype_id.loadAcquire())
return id;
const char * const cName = T::staticMetaObject.className();
const int newId = qRegisterNormalizedMetaType(
cName,
reinterpret_cast(quintptr(-1)));
metatype_id.storeRelease(newId);
return newId;
}
};
template
struct QMetaTypeIdQObject
{
enum {
Defined = 1
};
static int qt_metatype_id()
{
static QBasicAtomicInt metatype_id = Q_BASIC_ATOMIC_INITIALIZER(0);
if (const int id = metatype_id.loadAcquire())
return id;
const char * const cName = T::staticMetaObject.className();
QByteArray typeName;
typeName.reserve(int(strlen(cName)) + 1);
typeName.append(cName).append('*');
const int newId = qRegisterNormalizedMetaType(
typeName,
reinterpret_cast(quintptr(-1)));
metatype_id.storeRelease(newId);
return newId;
}
};
template
struct QMetaTypeIdQObject
{
enum {
Defined = 1
};
static int qt_metatype_id()
{
static QBasicAtomicInt metatype_id = Q_BASIC_ATOMIC_INITIALIZER(0);
if (const int id = metatype_id.loadAcquire())
return id;
const char *eName = qt_getEnumName(T());
const char *cName = qt_getEnumMetaObject(T())->className();
QByteArray typeName;
typeName.reserve(int(strlen(cName) + 2 + strlen(eName)));
typeName.append(cName).append("::").append(eName);
const int newId = qRegisterNormalizedMetaType(
typeName,
reinterpret_cast(quintptr(-1)));
metatype_id.storeRelease(newId);
return newId;
}
};
QMetaTypeIdQObject专门用于处理 Qt 特定类型(QObject 派生类、Qt Gadget、枚举类型)的元类型信息。其核心设计思路是:通过编译时类型判断(借助 QtPrivate 辅助模板)和模板特化,为不同类型定制元类型注册逻辑,最终为 QMetaTypeId<T> 提供底层支持(因为 QMetaTypeId<T> : public QMetaTypeIdQObject<T>)。
主模板:编译时类型分类与默认退化:
template ::Value ? QMetaType::PointerToQObject :
QtPrivate::IsGadgetHelper::IsRealGadget ? QMetaType::IsGadget :
QtPrivate::IsPointerToGadgetHelper::IsRealGadget ? QMetaType::PointerToGadget :
QtPrivate::IsQEnumHelper::Value ? QMetaType::IsEnumeration : 0>
struct QMetaTypeIdQObject
{
enum { Defined = 0 };
};
其核心作用是:
- 编译时类型判断:通过
QtPrivate系列辅助模板(编译时类型 traits),为类型T分配一个「类型标志」(第二个模板参数,int类型); - 默认退化:主模板本身不支持任何类型(
Defined = 0),仅当T匹配某个特化版本时,才会启用对应的元类型支持。
关键:编译时类型标志计算(第二个模板参数)
第二个模板参数是「编译时表达式」,通过 QtPrivate 辅助模板判断 T 的类型归属,最终返回以下标志之一(QMetaType::TypeFlags 枚举):
| 辅助模板 | 类型标志 | 对应 T 的类型 |
|---|---|---|
IsPointerToTypeDerivedFromQObject<T>::Value | QMetaType::PointerToQObject | T 是 QObject 派生类的指针(如 MyWidget*) |
IsGadgetHelper<T>::IsRealGadget | QMetaType::IsGadget | T 是 Qt Gadget 类型(带 Q_GADGET 宏,无父类) |
IsPointerToGadgetHelper<T>::IsRealGadget | QMetaType::PointerToGadget | T 是 Qt Gadget 类型的指针(如 MyGadget*) |
IsQEnumHelper<T>::Value | QMetaType::IsEnumeration | T 是 Qt 枚举类型(带 Q_ENUM/Q_FLAG 宏) |
| 以上均不匹配 | 0 | 普通类型(主模板生效,Defined=0) |
QtPrivate 辅助模板的本质:是 Qt 内部实现的「编译时类型判断工具」,通过 SFINAE、std::enable_if 等 C++ 模板技巧,在编译时推断 T 的类型特征(无需运行时计算)。例如:
IsPointerToTypeDerivedFromQObject<T>:判断T是否为「指向 QObject 派生类的指针」;IsGadgetHelper<T>:判断T是否为「真实的 Qt Gadget 类型」(需带Q_GADGET宏且非 QObject 派生)。
特化版本:为不同类型定制元类型注册逻辑
主模板仅负责「类型分类」,真正的元类型支持(Defined=1 和 qt_metatype_id() 实现)由以下 4 个特化版本 提供,分别对应 QObject 指针、Gadget 值类型、Gadget 指针、枚举类型。
1. 特化版本 1:QObject 派生类指针(T*, QMetaType::PointerToQObject)
template
struct QMetaTypeIdQObject
{
enum { Defined = 1 }; // 标记为支持的元类型
static int qt_metatype_id()
{
static QBasicAtomicInt metatype_id = Q_BASIC_ATOMIC_INITIALIZER(0);
if (const int id = metatype_id.loadAcquire()) // 原子加载:避免重复注册
return id;
// 生成类型名:QObject类名 + "*"(如 "MyWidget*")
const char * const cName = T::staticMetaObject.className();
QByteArray typeName;
typeName.reserve(int(strlen(cName)) + 1);
typeName.append(cName).append('*');
// 注册规范化类型(支持 const T*& 等修饰形式的统一识别)
const int newId = qRegisterNormalizedMetaType(
typeName,
reinterpret_cast(quintptr(-1))); // 无效指针:辅助类型推断
metatype_id.storeRelease(newId); // 原子存储:确保线程可见性
return newId;
}
};
- 适用场景:
T是 QObject 派生类(如MyWidget),模板参数为T*(指针类型); - 类型名生成:通过
T::staticMetaObject.className()获取 QObject 类名(如MyWidget),拼接*生成类型名(如MyWidget*),确保指针类型的唯一性; - 线程安全:用
QBasicAtomicInt原子变量缓存 ID,loadAcquire/storeRelease保证多线程环境下「仅注册一次」,避免重复注册和数据竞争; - 注册方式:调用
qRegisterNormalizedMetaType(而非qRegisterMetaType),确保const MyWidget*、MyWidget*&等修饰形式被统一识别为MyWidget*。
2.特化版本 2:Qt Gadget 值类型(T, QMetaType::IsGadget)
template
struct QMetaTypeIdQObject
{
enum { Defined = std::is_default_constructible::value }; // 依赖默认构造函数
static int qt_metatype_id()
{
static QBasicAtomicInt metatype_id = Q_BASIC_ATOMIC_INITIALIZER(0);
if (const int id = metatype_id.loadAcquire())
return id;
// 生成类型名:Gadget类名(如 "MyGadget")
const char * const cName = T::staticMetaObject.className();
const int newId = qRegisterNormalizedMetaType(
cName,
reinterpret_cast(quintptr(-1)));
metatype_id.storeRelease(newId);
return newId;
}
};
- 适用场景:
T是 Qt Gadget 类型(带Q_GADGET宏,支持元属性但无信号槽,非 QObject 派生); Defined条件:std::is_default_constructible<T>::value—— Gadget 必须有默认构造函数(元系统需运行时创建对象);- 类型名生成:直接使用 Gadget 的类名(如
MyGadget),无需拼接指针符号(因为是值类型)。
3.特化版本 3:Qt Gadget 指针类型(T*, QMetaType::PointerToGadget)
template
struct QMetaTypeIdQObject
{
enum { Defined = 1 };
static int qt_metatype_id()
{
static QBasicAtomicInt metatype_id = Q_BASIC_ATOMIC_INITIALIZER(0);
if (const int id = metatype_id.loadAcquire())
return id;
// 生成类型名:Gadget类名 + "*"(如 "MyGadget*")
const char * const cName = T::staticMetaObject.className();
QByteArray typeName;
typeName.reserve(int(strlen(cName)) + 1);
typeName.append(cName).append('*');
const int newId = qRegisterNormalizedMetaType(
typeName,
reinterpret_cast(quintptr(-1)));
metatype_id.storeRelease(newId);
return newId;
}
};
- 适用场景:
T是 Qt Gadget 类型,模板参数为T*(指针类型); - 与 QObject 指针特化的区别:Gadget 无父子对象管理机制,但注册逻辑一致(生成指针类型名、原子缓存 ID、规范化注册)。
4.特化版本 4:Qt 枚举类型(T, QMetaType::IsEnumeration)
template
struct QMetaTypeIdQObject
{
enum { Defined = 1 };
static int qt_metatype_id()
{
static QBasicAtomicInt metatype_id = Q_BASIC_ATOMIC_INITIALIZER(0);
if (const int id = metatype_id.loadAcquire())
return id;
// 生成类型名:枚举所属类名::枚举名(如 "MyClass::MyEnum")
const char *eName = qt_getEnumName(T()); // 获取枚举名(如 "MyEnum")
const char *cName = qt_getEnumMetaObject(T())->className(); // 获取枚举所属类名(如 "MyClass")
QByteArray typeName;
typeName.reserve(int(strlen(cName) + 2 + strlen(eName))); // 预留 "::" 位置
typeName.append(cName).append("::").append(eName);
const int newId = qRegisterNormalizedMetaType(
typeName,
reinterpret_cast(quintptr(-1)));
metatype_id.storeRelease(newId);
return newId;
}
};
- 适用场景:
T是 Qt 枚举类型(带Q_ENUM或Q_FLAG宏,需在 QObject/Gadget 类中声明); - 类型名生成:格式为「枚举所属类名::枚举名」(如
MyClass::MyEnum),确保枚举类型的唯一性(避免不同类中同名枚举冲突); - 辅助函数:
qt_getEnumName和qt_getEnumMetaObject是 Qt 内部函数,通过枚举实例T()提取枚举的名称和所属元对象。
所有版本最后都是调用qRegisterNormalizedMetaType去注册数据类型的,下面来看一下qRegisterNormalizedMetaType的实现。
5.函数qRegisterNormalizedMetaType实现分析
源码如下:qmetatype.cpp
/*
Similar to QMetaType::type(), but only looks in the static set of types.
*/
static inline int qMetaTypeStaticType(const char *typeName, int length)
{
int i = 0;
while (types[i].typeName && ((length != types[i].typeNameLength)
|| memcmp(typeName, types[i].typeName, length))) {
++i;
}
return types[i].type;
}
static int registerNormalizedType(const NS(QByteArray) &normalizedTypeName,
QMetaType::Destructor destructor,
QMetaType::Constructor constructor,
QMetaType::TypedDestructor typedDestructor,
QMetaType::TypedConstructor typedConstructor,
int size, QMetaType::TypeFlags flags, const QMetaObject *metaObject)
{
QVector *ct = customTypes();
if (!ct || normalizedTypeName.isEmpty() || (!destructor && !typedDestructor) || (!constructor && !typedConstructor))
return -1;
int idx = qMetaTypeStaticType(normalizedTypeName.constData(),
normalizedTypeName.size());
int previousSize = 0;
QMetaType::TypeFlags::Int previousFlags = 0;
if (idx == QMetaType::UnknownType) {
QWriteLocker locker(customTypesLock());
int posInVector = -1;
idx = qMetaTypeCustomType_unlocked(normalizedTypeName.constData(),
normalizedTypeName.size(),
&posInVector);
if (idx == QMetaType::UnknownType) {
QCustomTypeInfo inf;
inf.typeName = normalizedTypeName;
#ifndef QT_NO_DATASTREAM
inf.loadOp = 0;
inf.saveOp = 0;
#endif
inf.alias = -1;
inf.typedConstructor = typedConstructor;
inf.typedDestructor = typedDestructor;
inf.constructor = constructor;
inf.destructor = destructor;
inf.size = size;
inf.flags = flags;
inf.metaObject = metaObject;
if (posInVector == -1) {
idx = ct->size() + QMetaType::User;
ct->append(inf);
} else {
idx = posInVector + QMetaType::User;
ct->data()[posInVector] = inf;
}
return idx;
}
if (idx >= QMetaType::User) {
previousSize = ct->at(idx - QMetaType::User).size;
previousFlags = ct->at(idx - QMetaType::User).flags;
// Set new/additional flags in case of old library/app.
// Ensures that older code works in conjunction with new Qt releases
// requiring the new flags.
if (flags != previousFlags) {
QCustomTypeInfo &inf = ct->data()[idx - QMetaType::User];
inf.flags |= flags;
if (metaObject)
inf.metaObject = metaObject;
}
}
}
if (idx < QMetaType::User) {
previousSize = QMetaType::sizeOf(idx);
previousFlags = QMetaType::typeFlags(idx);
}
if (Q_UNLIKELY(previousSize != size)) {
qFatal("QMetaType::registerType: Binary compatibility break "
"-- Size mismatch for type '%s' [%i]. Previously registered "
"size %i, now registering size %i.",
normalizedTypeName.constData(), idx, previousSize, size);
}
// these flags cannot change in a binary compatible way:
const int binaryCompatibilityFlag = QMetaType::PointerToQObject | QMetaType::IsEnumeration | QMetaType::SharedPointerToQObject
| QMetaType::WeakPointerToQObject | QMetaType::TrackingPointerToQObject;
if (Q_UNLIKELY((previousFlags ^ flags) & binaryCompatibilityFlag)) {
const char *msg = "QMetaType::registerType: Binary compatibility break. "
"\nType flags for type '%s' [%i] don't match. Previously "
"registered TypeFlags(0x%x), now registering TypeFlags(0x%x). ";
qFatal(msg, normalizedTypeName.constData(), idx, previousFlags, int(flags));
}
return idx;
}
它的主要作用是在运行时将自定义类型(或扩展类型)注册到 Qt 元类型系统中,并确保类型信息的二进制兼容性,同时处理 “规范化类型名” 与元类型 ID 的绑定。
1.初始化与参数校验
customTypes():返回存储自定义类型信息的全局向量(QVector<QCustomTypeInfo>),QCustomTypeInfo是存储类型元信息的结构体(包含类型名、构造 / 析构函数、大小等)。- 参数校验:若自定义类型向量不存在、类型名空、或缺少构造 / 析构函数(两者至少需有一个),则注册失败,返回
-1。
2.检查是否为内置静态类型
MetaTypeStaticType:检查normalizedTypeName是否对应 Qt 内置类型(如int、QString),若存在则返回其预定义的元类型 ID(如int返回 2),否则返回QMetaType::UnknownType。
3.处理未注册的新类型(idx为UnknownType)
- 线程安全:通过
QWriteLocker和customTypesLock()确保多线程环境下对customTypes向量的修改安全。 - ID 分配规则:自定义类型 ID 从
QMetaType::User(默认 256)开始递增,确保与内置类型 ID(<256)不冲突。 - 信息存储:
QCustomTypeInfo记录类型的核心信息,供后续QMetaType创建 / 销毁对象(如QVariant::setValue时调用构造函数)。
4.处理已注册的自定义类型(idx >= QMetaType::User)
- 若类型已注册(ID 在自定义类型范围内),则更新其标志(合并新标志,避免覆盖),并确保元对象正确。
5.处理内置类型(idx < QMetaType::User)
if (idx < QMetaType::User) {
previousSize = QMetaType::sizeOf(idx); // 内置类型的预定义大小
previousFlags = QMetaType::typeFlags(idx); // 内置类型的预定义标志
}
- 若类型是内置类型(如
int),则获取其预定义的大小和标志(用于后续兼容性检查)。
6.二进制兼容性检查(核心!)
// 检查类型大小是否匹配(大小不匹配会破坏二进制兼容)
if (Q_UNLIKELY(previousSize != size)) {
qFatal("QMetaType::registerType: Binary compatibility break -- Size mismatch for type '%s' [%i]. Previously registered size %i, now registering size %i.",
normalizedTypeName.constData(), idx, previousSize, size);
}
// 检查关键标志是否匹配(影响二进制兼容的标志)
const int binaryCompatibilityFlag = QMetaType::PointerToQObject | QMetaType::IsEnumeration | QMetaType::SharedPointerToQObject
| QMetaType::WeakPointerToQObject | QMetaType::TrackingPointerToQObject;
if (Q_UNLIKELY((previousFlags ^ flags) & binaryCompatibilityFlag)) {
const char *msg = "QMetaType::registerType: Binary compatibility break. \nType flags for type '%s' [%i] don't match. Previously registered TypeFlags(0x%x), now registering TypeFlags(0x%x). ";
qFatal(msg, normalizedTypeName.constData(), idx, previousFlags, int(flags));
}
- 类型大小检查:同一类型的大小在注册前后必须一致(例如,若
MyType之前注册时大小为 16 字节,现在修改为 24 字节,会导致内存布局不兼容,直接qFatal终止程序)。 - 关键标志检查:与二进制兼容强相关的标志(如是否为 QObject 指针、是否为枚举)必须一致。这些标志决定了 Qt 元系统如何处理该类型(如内存管理、转换逻辑),不一致会导致运行时错误。
7.返回注册成功的类型 ID
- 最终返回类型的元类型 ID(无论是新注册的自定义类型,还是已注册的内置 / 自定义类型)。
6.QVariant的canConvert实现分析
template
bool canConvert() const
{ return canConvert(qMetaTypeId()); }
bool QVariant::canConvert(int targetTypeId) const
{
if (d.type == targetTypeId)
return true;
#if QT_CONFIG(itemmodel)
if ((targetTypeId == QMetaType::QModelIndex && d.type == QMetaType::QPersistentModelIndex)
|| (targetTypeId == QMetaType::QPersistentModelIndex && d.type == QMetaType::QModelIndex))
return true;
#endif
if (targetTypeId == QMetaType::QVariantList
&& (d.type == QMetaType::QVariantList
|| d.type == QMetaType::QStringList
|| d.type == QMetaType::QByteArrayList
|| QMetaType::hasRegisteredConverterFunction(d.type,
qMetaTypeId()))) {
return true;
}
if ((targetTypeId == QMetaType::QVariantHash || targetTypeId == QMetaType::QVariantMap)
&& (d.type == QMetaType::QVariantMap
|| d.type == QMetaType::QVariantHash
|| QMetaType::hasRegisteredConverterFunction(d.type,
qMetaTypeId()))) {
return true;
}
if (targetTypeId == qMetaTypeId >() &&
QMetaType::hasRegisteredConverterFunction(d.type,
qMetaTypeId())) {
return true;
}
if ((d.type >= QMetaType::User || targetTypeId >= QMetaType::User)
&& QMetaType::hasRegisteredConverterFunction(d.type, targetTypeId)) {
return true;
}
// TODO Reimplement this function, currently it works but it is a historical mess.
uint currentType = d.type;
if (currentType == QMetaType::SChar || currentType == QMetaType::Char)
currentType = QMetaType::UInt;
if (targetTypeId == QMetaType::SChar || currentType == QMetaType::Char)
targetTypeId = QMetaType::UInt;
if (currentType == QMetaType::Short || currentType == QMetaType::UShort)
currentType = QMetaType::Int;
if (targetTypeId == QMetaType::Short || currentType == QMetaType::UShort)
targetTypeId = QMetaType::Int;
if (currentType == QMetaType::Float)
currentType = QMetaType::Double;
if (targetTypeId == QMetaType::Float)
targetTypeId = QMetaType::Double;
if (currentType == uint(targetTypeId))
return true;
if (targetTypeId < 0)
return false;
if (targetTypeId >= QMetaType::User) {
if (QMetaType::typeFlags(targetTypeId) & QMetaType::IsEnumeration) {
targetTypeId = QMetaType::Int;
} else {
return canConvertMetaObject(currentType, targetTypeId, d.data.o);
}
}
if (currentType == QMetaType::QJsonValue || targetTypeId == QMetaType::QJsonValue) {
switch (currentType == QMetaType::QJsonValue ? targetTypeId : currentType) {
case QMetaType::Nullptr:
case QMetaType::QString:
case QMetaType::Bool:
case QMetaType::Int:
case QMetaType::UInt:
case QMetaType::Double:
case QMetaType::Float:
case QMetaType::ULong:
case QMetaType::Long:
case QMetaType::LongLong:
case QMetaType::ULongLong:
case QMetaType::UShort:
case QMetaType::UChar:
case QMetaType::Char:
case QMetaType::SChar:
case QMetaType::Short:
case QMetaType::QVariantList:
case QMetaType::QVariantMap:
case QMetaType::QVariantHash:
case QMetaType::QCborValue:
case QMetaType::QCborArray:
case QMetaType::QCborMap:
return true;
default:
return false;
}
}
if (currentType == QMetaType::QJsonArray)
return targetTypeId == QMetaType::QVariantList || targetTypeId == QMetaType::QCborValue
|| targetTypeId == QMetaType::QCborArray;
if (currentType == QMetaType::QJsonObject)
return targetTypeId == QMetaType::QVariantMap || targetTypeId == QMetaType::QVariantHash
|| targetTypeId == QMetaType::QCborValue || targetTypeId == QMetaType::QCborMap;
if (currentType == QMetaType::QCborValue || targetTypeId == QMetaType::QCborValue) {
switch (currentType == QMetaType::QCborValue ? targetTypeId : currentType) {
case QMetaType::UnknownType:
case QMetaType::Nullptr:
case QMetaType::Bool:
case QMetaType::Int:
case QMetaType::UInt:
case QMetaType::Double:
case QMetaType::Float:
case QMetaType::ULong:
case QMetaType::Long:
case QMetaType::LongLong:
case QMetaType::ULongLong:
case QMetaType::UShort:
case QMetaType::UChar:
case QMetaType::Char:
case QMetaType::SChar:
case QMetaType::Short:
case QMetaType::QString:
case QMetaType::QByteArray:
case QMetaType::QDateTime:
case QMetaType::QUrl:
case QMetaType::QRegularExpression:
case QMetaType::QUuid:
case QMetaType::QVariantList:
case QMetaType::QVariantMap:
case QMetaType::QVariantHash:
case QMetaType::QJsonValue:
case QMetaType::QJsonArray:
case QMetaType::QJsonObject:
case QMetaType::QJsonDocument:
case QMetaType::QCborArray:
case QMetaType::QCborMap:
case QMetaType::QCborSimpleType:
return true;
default:
return false;
}
}
if (currentType == QMetaType::QCborArray)
return targetTypeId == QMetaType::QVariantList || targetTypeId == QMetaType::QCborValue
|| targetTypeId == QMetaType::QJsonArray;
if (currentType == QMetaType::QCborMap)
return targetTypeId == QMetaType::QVariantMap || targetTypeId == QMetaType::QVariantHash
|| targetTypeId == QMetaType::QCborValue || targetTypeId == QMetaType::QJsonObject;
// FIXME It should be LastCoreType intead of Uuid
if (currentType > int(QMetaType::QUuid) || targetTypeId > int(QMetaType::QUuid)) {
switch (uint(targetTypeId)) {
case QVariant::Int:
if (currentType == QVariant::KeySequence)
return true;
Q_FALLTHROUGH();
case QVariant::UInt:
case QVariant::LongLong:
case QVariant::ULongLong:
return currentType == QMetaType::ULong
|| currentType == QMetaType::Long
|| currentType == QMetaType::UShort
|| currentType == QMetaType::UChar
|| currentType == QMetaType::Char
|| currentType == QMetaType::SChar
|| currentType == QMetaType::Short
|| QMetaType::typeFlags(currentType) & QMetaType::IsEnumeration;
case QVariant::Image:
return currentType == QVariant::Pixmap || currentType == QVariant::Bitmap;
case QVariant::Pixmap:
return currentType == QVariant::Image || currentType == QVariant::Bitmap
|| currentType == QVariant::Brush;
case QVariant::Bitmap:
return currentType == QVariant::Pixmap || currentType == QVariant::Image;
case QVariant::ByteArray:
return currentType == QVariant::Color || currentType == QMetaType::Nullptr
|| ((QMetaType::typeFlags(currentType) & QMetaType::IsEnumeration) && QMetaType::metaObjectForType(currentType));
case QVariant::String:
return currentType == QVariant::KeySequence || currentType == QVariant::Font
|| currentType == QVariant::Color || currentType == QMetaType::Nullptr
|| ((QMetaType::typeFlags(currentType) & QMetaType::IsEnumeration) && QMetaType::metaObjectForType(currentType));
case QVariant::KeySequence:
return currentType == QVariant::String || currentType == QVariant::Int;
case QVariant::Font:
return currentType == QVariant::String;
case QVariant::Color:
return currentType == QVariant::String || currentType == QVariant::ByteArray
|| currentType == QVariant::Brush;
case QVariant::Brush:
return currentType == QVariant::Color || currentType == QVariant::Pixmap;
case QMetaType::Long:
case QMetaType::Char:
case QMetaType::SChar:
case QMetaType::UChar:
case QMetaType::ULong:
case QMetaType::Short:
case QMetaType::UShort:
return currentType == QVariant::Int
|| (currentType < qCanConvertMatrixMaximumTargetType
&& qCanConvertMatrix[QVariant::Int] & (1U << currentType))
|| QMetaType::typeFlags(currentType) & QMetaType::IsEnumeration;
case QMetaType::QObjectStar:
return canConvertMetaObject(currentType, targetTypeId, d.data.o);
default:
return false;
}
}
if (targetTypeId == String && currentType == StringList)
return v_cast(&d)->count() == 1;
return currentType < qCanConvertMatrixMaximumTargetType
&& qCanConvertMatrix[targetTypeId] & (1U << currentType);
}
1.直接类型匹配 (Exact Match)
这是最直接、最高效的检查。如果 QVariant 当前存储的类型 ID (d.type) 和目标类型 ID (targetTypeId) 完全一致,那么它肯定可以转换(实际上是无需转换)。
2.特殊类型对 (Special Type Pairs)
这部分处理一些 Qt 内部定义的、可以互相转换的特殊类型。
1) QModelIndex 和 QPersistentModelIndex
#if QT_CONFIG(itemmodel)
if ((targetTypeId == QMetaType::QModelIndex && d.type == QMetaType::QPersistentModelIndex)
|| (targetTypeId == QMetaType::QPersistentModelIndex && d.type == QMetaType::QModelIndex))
return true;
#endif
这允许 QVariant 在这两种模型索引类型之间无缝转换。
2) 容器类型推断 (Container Type Inference)
这部分代码非常巧妙,它允许 QVariant 将多种序列式或关联式容器 “看作”QVariantList 或 QVariantMap/QVariantHash。
// 检查是否可以转换为 QVariantList
if (targetTypeId == QMetaType::QVariantList
&& (d.type == QMetaType::QVariantList
|| d.type == QMetaType::QStringList
|| d.type == QMetaType::QByteArrayList
|| QMetaType::hasRegisteredConverterFunction(d.type,
qMetaTypeId()))) {
return true;
}
// 检查是否可以转换为 QVariantMap 或 QVariantHash
if ((targetTypeId == QMetaType::QVariantHash || targetTypeId == QMetaType::QVariantMap)
&& (d.type == QMetaType::QVariantMap
|| d.type == QMetaType::QVariantHash
|| QMetaType::hasRegisteredConverterFunction(d.type,
qMetaTypeId()))) {
return true;
}
- 如果
QVariant存储的是QStringList或QByteArrayList,它可以被隐式地转换为QVariantList(其元素分别是QString或QByteArray)。 - 更强大的是,它会检查是否存在一个注册的转换器函数,可以将当前类型转换为一个内部的 “可迭代接口”(
QSequentialIterableImpl或QAssociativeIterableImpl)。 - 任何注册了这种转换器的自定义容器类型,都可以被
QVariant当作QVariantList或QVariantMap来对待,从而可以使用toList()或toMap()等方法。
3) QPair<QVariant, QVariant>
if (targetTypeId == qMetaTypeId >() &&
QMetaType::hasRegisteredConverterFunction(d.type,
qMetaTypeId())) {
return true;
}
类似地,这允许将某些成对的数据结构转换为 QPair<QVariant, QVariant>。
3.注册的转换器函数 (Registered Converter Functions)
if ((d.type >= QMetaType::User || targetTypeId >= QMetaType::User)
&& QMetaType::hasRegisteredConverterFunction(d.type, targetTypeId)) {
return true;
}
这是 canConvert 扩展性的关键。对于任何自定义类型(ID >= QMetaType::User),你都可以通过 QMetaType::registerConverter() 函数注册一个自定义的转换函数。
如果从当前类型 d.type 到目标类型 targetTypeId 存在这样一个注册的函数,canConvert 会返回 true。
4.数值类型提升 (Numeric Type Promotion)
// TODO Reimplement this function, currently it works but it is a historical mess.
uint currentType = d.type;
if (currentType == QMetaType::SChar || currentType == QMetaType::Char)
currentType = QMetaType::UInt;
if (targetTypeId == QMetaType::SChar || currentType == QMetaType::Char)
targetTypeId = QMetaType::UInt;
if (currentType == QMetaType::Short || currentType == QMetaType::UShort)
currentType = QMetaType::Int;
if (targetTypeId == QMetaType::Short || currentType == QMetaType::UShort)
targetTypeId = QMetaType::Int;
if (currentType == QMetaType::Float)
currentType = QMetaType::Double;
if (targetTypeId == QMetaType::Float)
targetTypeId = QMetaType::Double;
if (currentType == uint(targetTypeId))
return true;
这部分代码处理了 C++ 中常见的数值类型隐式转换。例如:
char、short会被提升为int。float会被提升为double。
通过将源类型和目标类型都 “提升” 到一个更通用的类型后再进行比较,可以简化大量的数值转换检查。
5.枚举类型转换 (Enumeration Conversion)
if (targetTypeId < 0)
return false;
if (targetTypeId >= QMetaType::User) {
if (QMetaType::typeFlags(targetTypeId) & QMetaType::IsEnumeration) {
targetTypeId = QMetaType::Int;
} else {
return canConvertMetaObject(currentType, targetTypeId, d.data.o);
}
}
- 枚举类型在元类型系统中可以被当作整数处理。所以,如果目标类型是一个枚举,
canConvert会检查它是否可以转换为int。 canConvertMetaObject是一个内部函数,用于处理涉及QObject派生类指针的转换,例如检查一个QObject*是否可以被qobject_cast到目标类型。
6.JSON 和 CBOR 类型 (JSON & CBOR Types)
接下来的一长段 if 和 switch 语句专门处理 QJsonValue、QJsonArray、QJsonObject 以及 QCborValue 等相关类型的转换规则。
这些类型可以与许多基础类型(如 int, QString, QVariantList)相互转换,所以这里列举了所有支持的转换组合。
7.历史遗留和特殊情况 (Historical and Special Cases)
// FIXME It should be LastCoreType intead of Uuid
if (currentType > int(QMetaType::QUuid) || targetTypeId > int(QMetaType::QUuid)) {
switch (uint(targetTypeId)) {
// ... 大量 case 语句 ...
}
}
这部分包含了许多为了向后兼容而保留的特殊转换规则。例如:
Variant::KeySequence可以转换为int或QString。QVariant::Image、Pixmap、Bitmap之间可以互相转换。- 枚举类型可以转换为
int或QString。
8.最终的 fallback:转换矩阵 (Conversion Matrix)
if (targetTypeId == String && currentType == StringList)
return v_cast(&d)->count() == 1;
return currentType < qCanConvertMatrixMaximumTargetType
&& qCanConvertMatrix[targetTypeId] & (1U << currentType);
- 对于
QStringList转QString的特殊情况,只有当列表中只有一个元素时才允许转换。 - 最后,它会查询一个内部的转换矩阵
qCanConvertMatrix。这是一个 bitmask 矩阵,预定义了哪些核心类型可以转换到其他核心类型。这是一种高效的查找方式。
7.qRegisterMetaType禁止/不建议注册的类型
1.非 const 引用类型(T&)
- Qt 元类型系统不支持非 const 引用(
QMetaTypeId2<T&>::Defined = false),注册会触发编译错误; - 原因:非 const 引用可能导致对象生命周期管理混乱(如信号槽跨线程传递时临时对象被引用);
- 替代方案:使用
const T&(无需手动注册,元系统自动关联T的 ID)或值类型。
2.无默认构造 / 析构的类型
- 若
T无默认构造(如构造函数必须传参)或析构函数被禁用,元系统无法在运行时创建 / 销毁对象,注册后使用会崩溃; - 示例:
class NoDefaultCtor { public: NoDefaultCtor(int) {} };(禁止注册)。
3.匿名类型 / 局部类型
- 匿名结构体 / 类(如
struct { int x; })、函数内的局部类型,无法通过Q_DECLARE_METATYPE声明,注册会编译失败; - 原因:元系统需要明确的类型名作为唯一标识,匿名 / 局部类型无全局可见的类型名。
4.复杂模板类型(未声明元类型)
- 未通过
Q_DECLARE_METATYPE声明的复杂模板类型(如std::vector<T>,Qt 未预声明),注册会编译错误; - 注意:
std容器需手动声明(如Q_DECLARE_METATYPE(std::vector<int>)),但 Qt 更推荐使用 Qt 容器(如QVector)。
8.总结
数据类型的识别的本质是:
- 为自定义类型在 Qt 元系统中 “上户口”(ID 是身份证);
- 向运行时系统暴露操作该类型的 “工具”(构造 / 析构函数、大小等);
- 确保同一类型的不同修饰形式(如
const MyType&)能被统一识别。
正是通过这个流程,自定义类型才能被 Qt 运行时系统(信号槽、QVariant、动态属性等)正确识别和操作。
浙公网安备 33010602011771号