









元对象系统(Meta-Object System) moc是QT框架中的核心机制,非常核心,用于扩展C语言的动态特性. 简答来说,他是 C++语言和QT高级特性之间的"翻译官",负责将QT特有的语法(如 信号,槽,属性等)转换为标准的C++代码. 它主要是提供以下几个方面
它的工作流程大概是这样的
C++源码(.h) => MOC (Meta-Object Compiler)扫描 => 生成 moc_*.cpp文件 => 编译器编译 => 包含元对象信息的可执行文件
moc工具读取C++头文件,如果发现一个或者多个类声明包括Q_OBJECT宏,它会生成一个C++源文件,其中包这些类的元对象代码。信号和插槽机制,运行时类型信息,动态属性系统都需要元对象代码. moc生成的C++源文件必须与类的实现进行编译和链接.

首先我们要明白,QT框架为什么会有这么一套元对象系统MOS. 标准的C++是功能强大,性能卓越的静态类型语言.它的静态特性带来了一个局限 => 缺乏内置的运行时反射(Runtime Reflection)机制. 在标准的C++环境中,一旦代码被编译成机器码,类型的元信息(如类名,成员函数名,属性等)大多会丢失 => 这就导致在运行时无法通过字符串名称动态地查询和调用方法,访问属性等. 因此,QT对C++进行了扩展,包括三个核心组件
1. OObject基类: 任何希望利用元对象系统功能的类都必须(直接或间接)继承自QObject. 这个基类为对象提供了身份标识,父子关系管理及事件循环的集成能力
2. Q_Object 宏: 这个宏必须在类的私有部分声明. 它不是一个简单的标记,而是激活元对象特性的关键. 它会声明一些由元对象编译器(moc)实现的内部函数,包括 metaObject() (返回类的元对象),tr() (翻译国际化字符串), 信号-槽机制的实现
3. 元对象编译器(moc) : QT通过引入moc来扩展C++, 获得强大的动态特性。 正是因为moc在编译时生成了元数据,Qt才能在运行时通过名称(即字符串)进行方法查找,属性查询和信号-槽连接,从而实现C++本身无法企及的动态交互能力
QVariant -- Qt的类型安全联合体机制
在Qt的动态系统中,经常需要在不知道具体类型的情况下存储和传递数据. eg: QObject::setProperty()函数可以用字符串名称来设置一个属性,而这个属性的值可以是整数,字符串,颜色或其他任何类型。为了实现这种灵活性,QT提供了QVariant类. QVariant就像一个功能强大,类型安全的联合体, 它可以存储各种不同类型的值,从基本的int, double,到复杂的QString,QColor, 甚至是自定义类型 => QVariant的核心机制是类型擦除(Type Erasure), 它的存储机制大概是这样的:
我们把一个值存入QVariant, 它内部会保存2个东西,一个是数据本身的副本,另一个是类型标识符. 这个类型标识符是一个整数,它由QMetaType类统一管理,唯一地对应于被存储的数据类型
当从QVariant取回数据时,可以使用qvariant_cast<T>()模板函数或者value<T>()成员函数.这些函数会检查核实QVariant内部存储的类型是否和你要去获取的类型T是否是兼容的,如果一样就返回存储的数据。如果不兼容,就返回一个默认构造的值. => QVariant的这种机制对于实现动态属性系统和通过信号传递通用数据非常重要. 对于Qt内置的类型,这些操作都是预先定义好的. 但对于用户自定义类型,Qt的元对象系统在默认情况下对此一无所知. 那么对于自定义类型应该怎么办呢?=> 这里就牵涉出一个点,类型注册,我们要通过类型注册来连接静态C++和动态Qt
类型注册 => 就是将一个在静态C++世界中定义的普通类或者结构体,注入到Qt动态元对象系统的过程 => 这个过程为Qt提供了处理该类型所需的全部元信息
因为 1. Qt的元对象系统通过moc生成的元数据,在运行时实现了基于字符串的动态交互. 2. QVariant要进行安全的类型擦除和数据传递,就需要如何管理任意类型的生命周期(构造,复制,析构)
举个很简单的例子,现在有个C++中自定义的类Car, 它对Qt来说就是不透明的. Qt完全不知道它确切大小,如何给它分配内存,更不知道如何调用它的拷贝构造函数来创建一个安全的副本,不知道如何调用析构函数来释放资源. 所以,你贸然的把Car实例存入QVariant,会导致编译错误,因为它违反了类型安全的前提。
因此,自定义类型注册 在编译和运行两个层面,为Qt的动态核心提供了与用户自定义C++类型进行交互的“说明书”,通过这个过程,自定义类型得以被Qt元对象系统识别和管理, 从而使C++中的自定义类型能够像QString, QColor一样成为Qt框架中的一等公民,在QVariant, 信号-槽系统,属性系统中无缝使用.
注册自定义类型分为2步:
1. Q_DECLARE_METATYPE实现编译时感知
注册自定义类型的第一步是通过 Q_DECLARE_METATYPE()宏在编译时通知Qt该类型的存在.
比如针对我们在C++中自定义的类型Car, 现在我们来注册它,以便在Qt框架中使用 => 建立一个编译时宏 Q_DECLARE_METATYPE(Car), 它的核心作用是为Car创建一个QMetaTypeId模板的特化版本。 => 这个特化版本为Qt的模板化函数(比如 QVariant:fromValue() 和 qvariant_cast<>())提供了必要的信息,使它们在编译期间能识别和处理Car. => 也就是说,它在编译器层面建立了Car和Qt元类型系统之间的联系.
这个 Q_DECLARE_METATYPE()宏的使用必须遵循以下原则:
位置 - 该宏必须在全局命名空间范围内使用, 不能够把它放置在任何类或命名空间的定义内部
时间 - 它应该在头文件中,紧跟在自定义类型Car的完整定义之后 => 这保证任何包含该头文件的代码都能够立即知道该类型是一个元类型
// userinfo.h #include <QString> struct UserInfo { QString name; int age; }; // 关键:声明为元类型 Q_DECLARE_METATYPE(UserInfo)
// Car.h #include <QColor> class Car { public: Car() = default; Car(const QColor &color) : color(color) {} QColor color; }; // 声明为元类型 Q_DECLARE_METATYPE(Car)
完成了上面这一步,上面的UserInfo自定义类型和Car自定义类型,就获得了将数据存储于QVariant的能力
cpp UserInfo user = {"Luke",35}; QVariant variantUser = QVariant::fromValue(user); UserInfo getUser = variantUser.value<UserInfo>();
2. qRegisterMetaType用于运行时操作
上面第1步中的Q_DECLARE_METATYPE是必要的第一步,但我们通过上面例子也可以看到,它主要是在编译时来通知Qt框架. 但正如我们上面说的,我们想在运行时去获取自定义类型Car的信息,实现类似于C#中的反射功能 => 这个时候,我们就需要用qRegisterMetaType函数在运行时来完成注册
qRegisterMetaType<Car>()是一个运行时函数,它向Qt的元对象系统注册Car的详细信息,包括其名称(字符串类型), 构造函数,析构函数和拷贝构造函数 => 这些信息对于需要在运行时动态管理Car实例生命周期的场景是强制性的.
通过qRegisterMetaType<Car>把我们的自定义类型Car注册到Qt的元对象系统中,这样可以确保自定义类型Car不仅仅已知于QVariant,还能被整个QT系统熟知,尤其是用于多线程环境中信号和槽的QObject通信
通过qRegisterMetaType注册了Car类型后,QT就可以在运行时动态的创建,复制和销毁Car对象,即便Car是我们自定义的非QT类型。因为qRegisterMetaType在内部为Car类型存储了创建和析构它的对象需要的方法指针。这允许在运行时创建和复制Car类型对象传递给信号和槽作为参数,这些参数必须能够在事件循环中跨线程边界安全移动。
qRegisterMetaType被调用的时间 -- 它通常是在main函数或者类被实际用作信号和槽之前的初始化代码中调用
int main(int argc, char *argv[]) { QApplication app(argc, argv); // 注册到元类型系统 qRegisterMetaType<Car>("Car"); // 现在 Car可以安全地用做信号和槽参数 // ... return app.exec(); }
总结:
我们需要让C++中的一个自定义类型Car,在不同线程间的信号和槽通信中使用它. 那么,要做以下2步骤
1. 使用Q_DECLARE_METATYPE()声明这个类
// Car.h #include <QColor> class Car { public: Car() = default; Car(const QColor &color) : color(color) {} QColor color; }; // 声明为元类型 Q_DECLARE_METATYPE(Car)
2. 在应用程序启动时(eg: main函数中),使用qRegisterMetaType() 来注册Car类型
int main(int argc, char *argv[]) { QApplication app(argc, argv); // 注册到元类型系统 qRegisterMetaType<Car>("Car"); // 现在 Car可以安全地用做信号和槽参数 // ... return app.exec(); }
这样,Car就 可以在跨线程的信号和槽调用中安全的使用。
如果只是想在同一线程内的信号和槽或者使用 QVariant 存储自定义类型的话,通常只需要 Q_DECLARE_METATYPE 宏。
此外,qRegisterMetaType 还使得自定义类型Car可用于QMetaObject中的类型信息,比如QObject::property(),QObject::setProperty(),以及QMetaProperty::read()和QMetaProperty::write()这些反射相关的函数。
总之,Q_DECLARE_METATYPE 通知Qt元对象系统关于自定义类型的存在,这样该类型就可以在QVariant中使用。qRegisterMetaType在此基础上更进一步,它将自定义类型完全集成到Qt的元对象系统中,使得类型可以跨线程在信号和槽中使用,以及在Qt的属性系统中使用。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。