前言
Qt 的元对象系统(Meta-Object System)是 Qt 框架的核心之一,提供了一些 C++ 原生不具备的功能(因为在C++它们是静态的),如反射、信号槽机制、属性系统等。通过这个系统,Qt 实现了许多强大的功能,这使得它成为一个更易于使用和扩展的框架。
正文
元对象系统
1. 元对象系统的组成部分
1.1 Q_OBJECT 宏
- Q_OBJECT 是元对象系统的入口。任何需要使用元对象系统功能的类都必须包含这个宏。
- 它通常放在类的私有部分的顶部,并由 Qt 的元对象编译器(moc)处理,生成与类相关的元数据和代码。
class MyClass : public QObject {
Q_OBJECT
public:
MyClass(QObject *parent = nullptr) : QObject(parent) {}
signals:
void mySignal();
public slots:
void mySlot();
};
1.2 QMetaObject
- 元对象
QMetaObject
是用于描述另一个对象结构的对象,它提供了关于 QObject 类及其子类的元数据(如类名、信号、槽、属性等)。 - 可以通过调用
QObject::metaObject()
来获取与对象相关的元对象。
const QMetaObject *meta = myObject->metaObject();
qDebug() << "Class name:" << meta->className();
1.3 信号和槽(Signals and Slots)
- 信号槽机制是 Qt 中的核心通信方式。信号(signal)是用来发出事件通知的,而槽(slot)是用来处理这些事件的。
signals:
和slots:
关键字标识了类中的信号和槽函数,信号槽的连接可以在编译时或运行时完成。
QObject::connect(sender, &SenderClass::signalName, receiver, &ReceiverClass::slotName);
1.4 属性系统(Property System)
- 属性系统使得可以通过字符串名称访问和操作对象的属性,这在 QML 和动画系统中尤其有用。
- 使用
Q_PROPERTY
宏来定义属性。
class MyClass : public QObject {
Q_OBJECT
// 意思是value通过setValue这个函数来更新/设置这个值,在更新后发出通知信号valueChanged(int)
// 当属性值发生改变时,这个信号会被发出,通知所有连接到该信号的槽函数
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
public:
int value() const { return m_value; }
void setValue(int value) {
if (m_value == value)
return;
m_value = value;
emit valueChanged(m_value);
}
signals:
void valueChanged(int newValue);
private:
int m_value;
};
1.5 QMetaObject::invokeMethod
- 可以在运行时使用
QMetaObject::invokeMethod()
来调用对象的槽函数或其它成员函数。
MyClass obj;
QMetaObject::invokeMethod(&obj, "mySlot");
2. 元对象编译器(moc)
Qt 的元对象编译器 moc
是解析带有 Q_OBJECT
宏的文件。moc
若发现一个或多个包含了 Q_OBJECT
宏的类的声明,则会生成另外一个包含了Q_OBJECT
宏实现代码的 C++源文件(该源文件通常名称为 moc_*.cpp) ,这个新的源文件要么被#include 包含到类的源文件中,要么被编译键接到类的实现中(通常是使用的此种方法)。注意:新文件不会“替换”掉旧的文件,而是与原文件一起编译
moc
主要做了一下工作
- 生成一个静态的元对象实例,该实例包含类的元信息。
- 为每个信号生成一个函数,该函数可以发射该信号。
- 为类生成一个静态的成员函数,该函数可以返回静态的元对象实例。
3.反射机制
反射(Reflection)指的是程序在运行时检查和操作自身结构的能力。C++ (C++17好像支持,但是和Qt中的不同)本身不支持反射,但 Qt 通过元对象系统提供了一定程度的反射能力。这种能力主要体现在以下几个方面:
-
动态类型信息:
- 使用
QObject::metaObject()
可以在运行时获取与类相关的元数据(如类名、信号、槽、属性等)。
- 使用
-
动态属性访问:
- 通过
QObject::setProperty()
和QObject::property()
方法,可以通过字符串名称在运行时访问和修改对象的属性。
- 通过
-
信号与槽的动态连接:
- 使用
QObject::connect()
函数,可以在运行时通过字符串名称来动态连接信号和槽。这使得信号和槽的连接可以在运行时根据条件来建立或改变。
- 使用
-
动态对象创建:
- 使用
QMetaObject::newInstance()
可以在运行时根据类的元对象创建新的对象实例(前提是类中有符合条件的构造函数)。
- 使用
Qt 的元对象系统通过元对象编译器(moc)生成附加的代码,允许在运行时获取类的元数据,并使用这些元数据实现类似反射的功能。这种机制在实现动态特性、插件系统和QML绑定等功能时非常有用。
4. 元对象系统的使用
元对象系统的使用需要满足三个条件
- 该类必须继承自
QObject
或者继承自继承QObject
类的子类 - 该类在声明
Q_OBJECT
这个宏时,必须在私有区域进行声明 - 元对象编译器(moc)为每个
QObject
的子类,提供了实现员特性所必须的代码
MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QObject>
#include <QDebug>
// MyClass 是一个示例类,展示了 Qt 元对象系统的使用
class MyClass : public QObject {
Q_OBJECT
// 定义一个属性 "value",可以通过 getter (value) 和 setter (setValue) 访问,
// 当属性值发生变化时发出信号 valueChanged
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
public:
explicit MyClass(QObject *parent = nullptr);
// 属性的 getter 函数
int value() const;
// 属性的 setter 函数
void setValue(int newValue);
signals:
// 当属性值发生变化时发出的信号
void valueChanged(int newValue);
public slots:
// 一个槽函数,用于打印当前属性值
void printValue();
private:
int m_value; // 用于存储属性值的成员变量
};
#endif // MYCLASS_H
MyClass.cpp
#include "MyClass.h"
// 构造函数,初始化属性值为 0
MyClass::MyClass(QObject *parent) : QObject(parent), m_value(0) {}
// getter 函数,返回当前的属性值
int MyClass::value() const {
return m_value;
}
// setter 函数,设置属性值,并发出 valueChanged 信号(如果值发生变化)
void MyClass::setValue(int newValue) {
if (m_value != newValue) {
m_value = newValue;
emit valueChanged(m_value);
}
}
// 槽函数,打印当前属性值
void MyClass::printValue() {
qDebug() << "The value is:" << m_value;
}
main.cpp
#include <QCoreApplication>
#include <QMetaObject>
#include <QMetaProperty>
#include <QMetaMethod>
#include "MyClass.h"
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
// 动态创建 MyClass 对象
QObject *obj = QMetaObject::newInstance(MyClass::staticMetaObject);
// 检查对象是否创建成功
if (!obj) {
qDebug() << "Failed to create the object!";
return -1;
}
// 获取对象的元对象信息
const QMetaObject *metaObj = obj->metaObject();
qDebug() << "Class Name:" << metaObj->className();
// 动态访问和修改属性
int propertyIndex = metaObj->indexOfProperty("value");
if (propertyIndex != -1) {
obj->setProperty("value", 42); // 设置属性值
qDebug() << "Property 'value':" << obj->property("value").toInt(); // 获取属性值
}
// 动态连接信号和槽
int signalIndex = metaObj->indexOfSignal("valueChanged(int)");
int slotIndex = metaObj->indexOfSlot("printValue()");
if (signalIndex != -1 && slotIndex != -1) {
QMetaObject::connect(obj, signalIndex, obj, slotIndex);
}
// 修改属性值,这将触发 valueChanged 信号,并调用 printValue 槽函数
obj->setProperty("value", 100);
// 清理动态创建的对象
delete obj;
return a.exec();
}
代码解释
MyClass.h
Q_OBJECT
: 必须放在类的定义中,用于启用 Qt 的元对象系统。Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
: 定义一个名为value
的属性,指定了 getter (value
)、setter (setValue
) 和属性变化时发出的信号 (valueChanged
)。signals:
: 定义信号valueChanged
,当value
属性发生变化时发出。public slots:
: 定义槽函数printValue
,用于打印属性值。
MyClass.cpp
MyClass::MyClass(QObject *parent)
: 构造函数,初始化m_value
为 0。value()
: 返回当前的m_value
。setValue(int newValue)
: 设置m_value
的值,并在值发生变化时发出valueChanged
信号。printValue()
: 打印当前的m_value
。
main.cpp
QMetaObject::newInstance(MyClass::staticMetaObject)
: 动态创建MyClass
的实例。MyClass::staticMetaObject
提供了类的元对象信息。metaObject()->className()
: 获取并打印类名。metaObject()->indexOfProperty("value")
: 获取属性value
的索引。通过索引动态设置和获取属性值。QMetaObject::connect()
: 动态连接信号valueChanged(int)
和槽printValue()
。obj->setProperty("value", 100)
: 修改属性值,触发valueChanged
信号,进而调用printValue
槽函数。
注意:若定义了QObject
类的派生类,并进行了构建,在这之后再添加 Q_OBJECT
宏,则此时
必须执行一次 qmake
命令(“构建”>“执行 qmake
”),否则 moc
不能生成代码。