Qt C++ :元对象系统(QObject类、信号槽、对象树、属性系统)
Qt中引入了元对象系统对标准C++语言进行了扩展,增加了对象间的通信机制(信号和槽)、动态属性系统、运行时类型信息、动态翻译等特性,它使Qt能够更好的实现GUI用户图形界面编程。
Qt的元对象系统不支持C++模板。
元对象系统概述
Qt元对象系统基于以下3个事实:
- 基类
QObject:任何需要使用元对象系统功能的类必须继承自QObject。QObject是Qt中最基本的类,是所有Qt对象的基类。 Q_OBJECT宏:Q_OBJECT宏必须出现在类的私有声明区,用于启动元对象的特性。- 元对象编译器(moc, Meta-Object Compiler):为
QObject子类实现元对象特性提供必要的代码实现。(将Qt对C++的语法拓展编译还原为标准C++代码)
构建项目时,MOC会读取C++源文件,当他发现类的定义中有Q_OBJECT宏时,他就会为这个类生成另一个包含元对象支持代码的C++源文件,这个生成的源文件连同类的实现文件一起被标准C++编译器编译和链接。
QObject类和QMetaObject类
QObject类是所有使用元对象系统的类的基类,也就是说,如果一个类的父类或上层父类是QObject,它就可以使用信号与槽、属性等特性。
元对象系统的特性是通过QObject的一些函数来实现的。
(1) 元对象(meta object)。每个QObject及其子类的实例都有一个元对象,这个元对象是自动创建的。静态变量staticMetaObject就是这个元对象,函数metaObject()返回这个元对象指针。
所以,获取一个对象的元对象有两种方式,示意代码如下:
QPushButton *btn= new QPushButton();
const QMetaObject *metaPtr = btn->metaObject();//获取元对象指针
const QMetaObject metaObj = btn->staticMetaObject;//获取元对象
(2) 类型信息。QObject的inherits()函数可以判断对象是不是从某个类继承的类的实例。
(3) 动态翻译。函数tr()用于返回一个字符串的翻译版本,在设计多语言界面的应用程序时需要用到tr()函数。
(4) 对象树(object tree)。对象树指的是表示对象间从属关系的树状结构。例如在一个窗口上,组件都有父容器,窗口是界面上所有组件的顶层容器。QObject类的parent()函数返回其父对象,children()函数返回其子对象,findChildren()函数可以返回某些子对象或所有子对象。
窗口和窗口上的组件就构成对象树,窗口可以访问任何一个界面组件。对象树中的某个对象被删除时,它的子对象会被自动删除,所以,一个窗口被删除时,它上面的所有界面组件也会被自动删除。
(5) 信号与槽。通过在一个类的定义中插入宏Q_OBJECT,我们就可以使用Qt扩展的C++语言特性编程,例如在一个类中定义属性、类信息、信号和槽函数。
(6) 属性系统。在类的定义代码中可以用宏Q_PROPERTY定义属性,QObject的setProperty()函数会设置属性的值或定义动态属性;property()函数会返回属性的值。
每个QObject及其子类的实例都有一个自动创建的元对象,元对象是QMetaObject类型的实例。元对象存储了类的实例所属类的各种元数据,包括类信息元数据、方法元数据、属性元数据等,所以,元对象实质上是对类的描述。
通过QMetaObject类的这些函数,我们可以在运行时获取一个QObject对象的类信息和各种元数据。例如,函数className()可返回类的名称,函数superClass()可返回其父类的元对象,函数newInstance()可以创建元对象所描述类的一个新的实例。
类的元数据又分为多种类型,且有专门的类来描述。例如,函数property()返回属性的元数据,属性元数据用QMetaProporty类描述,它的接口函数描述了属性的各种特性,如函数name()返回属性名称,函数type()返回属性数据类型。
运行时类型信息
通过使用QObject和QMetaObject提供的以下一些接口函数,我们可以在运行时获得一个对象的类名称以及其父类的名称,判断其是否从某个类继承而来。
要实现这些功能,并不需要C++编译器的运行时类型信息(run-time type information,RTTI)支持,
而是通过Qt自身的元对象编译器(moc)在编译时生成的元数据来实现。
1) 获取类名
通过 QObject::metaObject() 获取对象关联的 QMetaObject 实例,然后调用其 className() 方法,可以在运行时获取对象的类名称。
QObject *obj = new QPushButton();
qDebug() << obj->metaObject()->className(); // 输出: "QPushButton"
2) 查询继承关系QMetaObject 提供了方法来检查类的继承层次结构:
classInfo(int index):获取类的元信息(如版本、作者等自定义信息)。superClass():返回该类的父类的 QMetaObject 指针,可用于遍历继承链。
const QMetaObject *meta = obj->metaObject();
while (meta) {
qDebug() << "Class:" << meta->className();
meta = meta->superClass(); // 向上追溯父类
}
3) 判断类型继承关系
使用 QObject::inherits(const char *className) 方法,可以判断一个对象是否从指定的类继承而来。
QPushButton button;
qDebug() << button.inherits("QWidget"); // true
qDebug() << button.inherits("QAbstractButton"); // true
qDebug() << button.inherits("QLineEdit"); // false
此方法比C++的 dynamic_cast 更高效,且不依赖原生RTTI。
4) 安全的类型转换
虽然Qt不依赖C++ RTTI,但它提供了类似于 dynamic_cast 的功能:
qobject_cast<T>():类型安全的向下转型,仅适用于继承自 QObject 且声明了 Q_OBJECT 宏的类。
QObject *obj = new QPushButton();
QPushButton *btn = qobject_cast<QPushButton*>(obj);
if (btn) {
qDebug() << "Cast successful";
}
Qt的运行时类型信息完全由元对象系统提供,因此:
- 不需要在编译时开启C++的RTTI(如 -frtti)。
- 性能更高,因为
inherits()和metaObject()是基于字符串比较和指针查找,比typeid或dynamic_cast更轻量。 - 更加灵活,支持跨模块的类型查询。
元对象编译器(moc)的作用
所有这些功能的前提是类继承自 QObject 并声明了 Q_OBJECT 宏。moc 工具会解析这些宏,并生成包含元数据的C++代码,包括类名、信号/槽信息、属性、枚举等,这些数据在运行时通过 QMetaObject 暴露出来。
信号和槽
信号(signal)和槽(slot) 是Qt自行定义的一种对象间通信机制,实现对象之间的数据交互。
当用户或系统触发了一个动作,导致某个控件的状态发生了改变,该控件就会发射一个信号,即调用其类中一个特定的成员函数(信号),同时还能携带有必要的参数。
槽函数与普通成员函数没有太多区别,差别在于其功能。槽函数更多体现为对某种特定信号的处理,可以将槽和其他对象信号建立连接,这样当发射信号时,槽函数能将被触发和执行,进而来完成机体功能。
信号的定义
class XX: public QObject{
Q_OBJECT //宏,moc链接工具,元对象编译器,处理QT语法扩展,还原成标准c++代码
signals:
void signal_func(..); //信号函数,只需声明,不能写定义。QT语法扩展,普通C++无法编译
}
//发射信号
emit signal_func(..);
QObject::connect(A1, SIGNAL(sigfun1(int)), A2, SIGNAL(sigfun2(int)));
槽的定义
class XX: public QObject{
Q_OBJECT
public slots:
void slot_func(..){..} //槽函数可以连接到某个信号上,当信号被发射时,槽函数将被触发和执行。另外槽函数可以当作普通成员函数使用。
}
信号和槽的链接
QObject::connect(const QObject* sender, //信号发送对象指针
const char* signal, //要发送的的信号函数,可以使用“SIGNAL(..)”宏进行类型转换
const QObject* receiver, //信号的接收对象指针
const char* method); //要执行的槽函数,使用“SLOT(..)”宏进行类型转换
//信号函数连同参数类型转换为const char*
connect()函数的第五个参数Qt::ConnectionType
Qt::AutoConnection(默认值):如果信号的接收者和发送者在同一个线程中,就使用Qt::DirectConnection方式,否则使用Qt::QueuedConnection方式。在信号发射时自动确定关联方式。Qt::DirectConnection:信号被发射时槽函数立即执行,槽函数和信号在同一个线程中。Qt::QueuedConnection:在事件循环回到接收者线程后运行槽函数,槽函数和信号在不同线程中。Qt::BlockingQueuedConnection:与Qt::QueuedConnection相似,区别是信号线程会阻塞,直到槽函数运行完毕。当信号和槽函数在同一线程中时绝对不能使用这种方式,否则会造成死锁。
实例:
- 按钮点击时发送信号:clicked()
- 实现标签关闭功能:close()
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.resize(640, 640);
//创建标签控件
QLabel label("我是标签", &w);
label.move(20, 40);
//创建按钮空间,栈创建,系统自动销毁
QPushButton btn("我是按钮", &w);
btn.move(20, 100);
QPushButton btn2("退出", &w);
btn2.move(100, 100);
w.show();
//点击按钮关闭标签
QObject::connect(&btn, SIGNAL(clicked(bool)), &label, SLOT(close()));
//增加退出按钮,实现退出应用程序
QObject::connect(&btn2, SIGNAL(clicked(bool)),
&a, SLOT(quit()));
//&a, SLOT(closeAllWindows())
//&w, SLOT(close())
return a.exec();
}
信号和槽连接的语法要求与应用
-
信号和槽参数要一致
QObject::connect(A, SIGNAL(sigfun(int)), B, SLOT(slotfun(int))); QObject::connect(A, SIGNAL(sigfun(int)), B, SLOT(slotfun(int,int))); //error -
可以带有缺省参数
QObject::connect(A, SIGNAL(sigfun(int)), B, SLOT(slotfun(int,int=0))); -
信号函数参数可以多于槽函数,多于参数将被忽略
QObject::connect(A, SIGNAL(sigfun(int,int)), B, SLOT(slotfun(int))); -
信号和槽函数(一对多)
QObject::connect(A, SIGNAL(sigfun(int)), B1, SLOT(slotfun1(int))); QObject::connect(A, SIGNAL(sigfun(int)), B2, SLOT(slotfun2(int))); -
信号和槽函数(多对一)
QObject::connect(A1, SIGNAL(sigfun1(int)), B, SLOT(slotfun(int))); QObject::connect(A2, SIGNAL(sigfun2(int)), B, SLOT(slotfun(int))); -
两个信号直接连接,信号级联
QObject::connect(A1, SIGNAL(sigfun1(int)), A2, SLOT(slotfun2(int)));
对象树
Qt的对象树系统(Object Tree System) 是其内存管理与对象生命周期控制的核心机制之一。它通过建立 QObject 派生对象之间的父子关系,形成一棵运行时的对象树,从而实现自动内存管理、资源组织和事件传播等功能。
- 对象通过对象树的形式组织。
- 对象树主要用来内存回收,对象树之间不一定是继承关系
- Qt 对象间可以存在父子关系
- 每一个对象都保存有它所有子对象的指针
- 每一个对象都有一个指向其父对象的指针
- 可以使用
findChild()或findChildren()查找对象的子对象。
- 当指定 Qt 对象的父对象时,该对象将自动将自己添加到父对象的
children()列表中- 其父对象会在子对象链表中加入该对象的指针
- 该对象会保存指向其父对象的指针
- 当Qt 对象被销毁时
- 将自己从父对象的 Children List 移除
- 将自己的 Children List 中的所有对象销毁
Qt 提供了运行时接口来遍历对象树:
QObject *parent = obj->parent(); // 获取父对象
const QObjectList &children = parent->children(); // 获取所有子对象
for (QObject *child : children) {
qDebug() << "Child:" << child->objectName();
}
也可通过 findChild<T>() 和 findChildren<T>() 按名称或类型查找:
QPushButton *btn = parent->findChild<QPushButton*>("okButton");
QList<QLabel*> labels = parent->findChildren<QLabel*>(); // 所有 QLabel 子对象
与 GUI 组件的深度集成
在Qt Widgets中,对象树与可视化层级高度一致:
- 父 QWidget 显示其子 QWidget。
- 子控件随父控件移动、隐藏、显示、销毁。
- 布局管理器(QLayout)也利用对象树管理控件。
QWidget *window = new QWidget;
QPushButton *btn = new QPushButton("OK", window);
window->show(); // btn 自动显示在 window 上
属性系统
Qt的属性系统(Property System) 是其元对象系统(Meta-Object System)的重要组成部分,它提供了一种在运行时动态访问和操作对象属性的机制。
与C++原生的成员变量不同,Qt属性是通过元对象编译器(moc)注册到 QMetaObject 中的,并可以在运行时进行查询、读取、写入、监听等操作。
以下是Qt属性系统的核心特性总结:
1) 定义Qt属性
Qt属性不是普通的类成员变量,而是通过 Q_PROPERTY() 宏在类中声明的。该宏必须位于继承自 QObject 的类中,并且类需包含 Q_OBJECT 宏。
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged DESIGNABLE true)
public:
MyClass(QObject *parent = nullptr);
QString name() const { return m_name; }
void setName(const QString &name) {
if (m_name != name) {
m_name = name;
emit nameChanged();
}
}
int value() const { return m_value; }
void setValue(int val) {
if (m_value != val) {
m_value = val;
emit valueChanged();
}
}
signals:
void nameChanged();
void valueChanged();
private:
QString m_name;
int m_value;
};
2) Q_PROPERTY 宏参数详解
| 参数 | 说明 |
|---|---|
| READ | 指定读取属性值的函数(getter),必需 |
| WRITE | 指定设置属性值的函数(setter),可选 |
| NOTIFY | 属性变化时发出的信号,用于绑定和通知 |
| DESIGNABLE | 是否在设计工具(如Qt Designer)中可见 |
| SCRIPTABLE | 是否可在脚本中访问 |
| STORED | 是否“持久”存储(例如序列化) |
| USER | 是否为用户主要操作的属性(如 QTextEdit 的 text) |
| CONSTANT | 表示属性是常量(编译期已知,只读) |
| FINAL | 表示该属性不能被子类重写 |
3) 运行时动态访问属性
通过 QObject 提供的接口,可以在运行时动态操作属性:
获取和设置属性值:
MyClass obj;
obj.setProperty("name", "Alice"); // 设置属性
QString name = obj.property("name").toString(); // 获取属性
qDebug() << name; // 输出: "Alice"
查询所有可用属性:
const QMetaObject *meta = obj.metaObject();
for (int i = 0; i < meta->propertyCount(); ++i) {
QMetaProperty prop = meta->property(i);
qDebug() << "Property:" << prop.name()
<< "Type:" << prop.typeName()
<< "Value:" << obj.property(prop.name());
}
4) 属性的类型安全与转换
属性值通过 QVariant 类型进行封装,支持Qt内建类型(如 int, QString, QColor 等)以及自定义类型(需用 Q_DECLARE_METATYPE 注册)。
支持自动类型转换:
obj.setProperty("value", "42"); // 字符串自动转为 int
5) 属性变更通知(NOTIFY)
当属性值改变时,可通过 NOTIFY 指定的信号通知外部系统,这在以下场景中非常有用:
- QML 绑定:QML 可以监听属性变化并自动更新UI。
- 模型视图编程:视图组件可响应数据模型的属性变化。
- 动画系统:
QPropertyAnimation可对任意 NOTIFY 属性进行动画处理。
QPropertyAnimation *anim = new QPropertyAnimation(&obj, "value");
anim->setStartValue(0);
anim->setEndValue(100);
anim->start();
优点与使用场景
| 优点 | 说明 |
|---|---|
| 运行时反射 | 支持动态查询和修改对象属性 |
| 跨语言交互 | C++ 与 QML/JS 无缝通信的基础 |
| 高度可扩展 | 支持自定义类型和复杂逻辑 |
| 设计工具支持 | 集成于 Qt Designer 等可视化工具 |
| 动画与绑定支持 | 是 QPropertyAnimation 和数据绑定的核心 |
更多推荐



所有评论(0)