描述
Qt的元对象系统(Meta-Object System)是Qt框架的核心机制之一,它提供了运行时类型信息(RTTI)和信号与槽(Signals and Slots)机制的支持。元对象系统在Qt中扮演了很重要的角色,它使得Qt能够实现许多强大的功能,例如信号与槽的自动连接、QObject树结构的管理、对象的属性、对象之间的消息通信等。
元对象系统支持以下机制:
-
对象的类型信息:元对象系统允许在运行时获取对象的类型信息,包括类名、父类名、属性信息、信号与槽函数等。这使得我们可以在运行时通过对象指针来查询和操作对象的属性和函数。
-
信号与槽机制:元对象系统支持Qt独有的信号与槽机制,它提供了一种灵活、类型安全的方式来实现对象间的通信通过信号与槽,一个对象可以触发一个信号,而其他对象可以连接到该信号并执行相应的槽函数。
-
QMetaObject类:QMetaObject是元对象系统的核心类之一,它包含了类元信息,如类名、父类名、属性、信号与槽等。我们可以通过QMetaObject来查询和操作类的元信息,例如获取属性的值、连接信号与槽等。
-
Q_OBJECT宏:在使用元对象系统时,需要在类的声明中添加Q_OBJECT宏。它会自动生成元信息,并使得类具备信号与槽的功能。在构建项目时,moc(元对象编译器)会通过预器解析源代码,生成相关的元信息。
-
对象树结构管理:元对象系统支持QObject树结构的管理,即对象的父子关系。当一个QObject对象具有其他QObject对象作为其对象时,它会负责管理子对象的生命周期,并在其自身被销毁时自动销毁子对象。
总而言之,言而总之,元对象系统是Qt强大功能的基石,它不仅提供了类的元信息,还支持信号与槽机制、属性系统、对象树管理等重要功能。通过元对象系统,开发者更加方便地完成杂的应用程序开发,并实现可扩展和可维护的代码结构。
moc工具
Qt提供的moc(Meta Object Compiler)工具主要用于实现Qt中的元对象系统(Meta-Object System)。
在Qt中,元对象系统允许程序在运行时获取对象的属性、方法和信号,并且可以动态连接信号和槽。这是Qt框架的重要特性之一。moc工具的作用就是将使用了特殊宏的类或者函数处理成C++代码,为Qt的元对象系统提供必要的信息。
moc工具读取一个c++源文件。如果它发现一个或多个包含Q_OBJECT宏的类声明,它会生成另一个c++源文件,其中包含每个类的元对象代码。生成的源文件要么#include到类的源文件中,要么(更常见的是)编译并链接到类的实现中。
当我们在使用信号与槽、Q_OBJECT 宏、动态元属性、Q_DECLARE_INTERFACE 宏等 Qt 特有的功能时,就需要使用moc工具来处理相关的代码文件。moc会生成一个额外的源文件,其中包含元对象系统所需的信息,并且在构建时将其编译成目标文件。这样就可以在运行时使用元对象系统,实现Qt框架的各项功能。
总而言之,言而总之,moc工具是Qt框架元对象系统实现的重要组成部分,它为Qt应用程序提供了强大的动态功能和元数据支持。
实现
元对象系统基于以下三点:
- QObject类为可以利用元对象系统的对象提供了一个基类。
- 类声明的私有部分中的Q_OBJECT宏用于启用元对象特性,例如动态属性、信号和槽。
- 元对象编译器(moc)为每个QObject子类提供实现元对象特性所需的代码。
其他特性
除了提供对象之间通信的信号和槽机制(引入该系统的主要原因)之外,元对象代码还提供以下附加功能:
- QObject::metaObject()返回类的关联元对象。
- QMetaObject::className()在运行时以字符串形式返回类名,不需要通过c++编译器支持本机运行时类型信息(RTTI)。
- QObject::inherits()函数返回一个对象是否是继承了QObject继承树中指定类的类的实例。
- QObject::tr()和QObject::trUtf8()翻译字符串用于国际化。
- QObject::setProperty()和QObject::property()通过名称动态设置和获取属性。
- QMetaObject::newInstance()构造一个类的新实例。
qobject_case()转换
可以使用qobject_cast()对QObject类执行动态强制转换。qobject_cast()函数的行为类似于标准c++的dynamic_cast(),其优点是不需要RTTI支持,并且可以跨动态库边界工作。它试图将其参数强制转换为尖括号中指定的指针类型,如果对象的类型是正确的(在运行时确定),则返回非零指针,如果对象的类型不兼容则返回0。
例如,让我们假设MyWidget继承自QWidget,并使用Q_OBJECT宏声明:
QObject *obj = new MyWidget;
QObject *类型的obj变量实际上引用了一个MyWidget对象,因此我们可以适当地强制转换它:
QWidget *widget = qobject_cast<QWidget *>(obj);
从QObject到QWidget的强制转换是成功的,因为对象实际上是一个MyWidget,它是QWidget的一个子类。既然我们知道obj是一个MyWidget,我们也可以将它强制转换为MyWidget *:
MyWidget * MyWidget = qobject_cast<MyWidget *>(obj);
对MyWidget的强制转换是成功的,因为qobject_cast()没有区分内置Qt类型和自定义类型。
QLabel *label = qobject_cast<QLabel *>(obj);
// label is 0
另一方面,对QLabel的强制转换失败。然后将指针设置为0。这使得在运行时基于类型以不同方式处理不同类型的对象成为可能:
if (QLabel *label = qobject_cast<QLabel *>(obj)) {
label->setText(tr("Ping"));
} else if (QPushButton *button = qobject_cast<QPushButton *>(obj)) {
button->setText(tr("Pong!"));
}
虽然可以在不使用Q_OBJECT宏和元对象代码的情况下使用QObject作为基类,但是如果不使用Q_OBJECT宏,信号和槽以及这里描述的其他特性都将不可用。从元对象系统的角度来看,一个没有元代码的QObject子类相当于它最近的祖先带有元对象代码。这意味着,例如,QMetaObject::className()将不会返回类的实际名称,而是这个祖先的类名。
因此,强烈建议QObject的所有子类都使用Q_OBJECT宏,不管它们是否实际使用信号、槽和属性。
示例
.h
class MyObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged)
public:
MyObject(QObject *parent = nullptr) : QObject(parent), m_name(""), m_age(0) {}
QString name() const { return m_name; }
void setName(const QString &name) {
if (m_name != name) {
m_name = name;
emit nameChanged();
}
}
int age() const { return m_age; }
void setAge(int age) {
if (m_age != age) {
m_age = age;
emit ageChanged();
}
}
signals:
void nameChanged();
void ageChanged();
public slots:
void printInfo() {
qDebug() << "Name:" << m_name << "Age:" << m_age;
}
private:
QString m_name;
int m_age;
};
.cpp
MyObject obj;
obj.setObjectName("myObject");
const QMetaObject *metaObj = obj.metaObject();
int nameIdx = metaObj->indexOfProperty("name");
if (nameIdx != -1) {
QMetaProperty namePty = metaObj->property(nameIdx);
if (namePty.isReadable()) {
qDebug() << "Name:" << namePty.read(&obj).toString();
}
if (namePty.isWritable()) {
namePty.write(&obj, QVariant::fromValue(QString("Alice")));
qDebug() << "Set Name:" << obj.property("name").toString();
}
}
int ageIdx = metaObj->indexOfProperty("age");
if (ageIdx != -1) {
QMetaProperty agePty = metaObj->property(ageIdx);
if (agePty.isReadable()) {
qDebug() << "Age:" << agePty.read(&obj).toInt();
}
if (agePty.isWritable()) {
agePty.write(&obj, QVariant::fromValue(30));
qDebug() << "Set Age:" << obj.property("age").toInt();
}
}
int printIdx = metaObj->indexOfMethod("printInfo()");
if (printIdx != -1) {
QMetaMethod printMethod = metaObj->method(printIdx);
printMethod.invoke(&obj);
}
- 使用元对象系统获取 “MyObject” 类的属性和方法
- 使用 QMetaObject::indexOfProperty() 和 QMetaObject::property() 来获取属性
- 使用 QMetaObject::indexOfMethod() 和 QMetaObject::method() 来获取方法
- 可以在运行时读取和写入属性,或调用方法。
结果:
结论
如果今天生活欺骗了你,不要悲伤,不要哭泣,因为明天生活也会欺骗你
。