元对象系统
Qt 的元对象系统(Meta-Object System)是 Qt 框架的核心特性之一,它支持信号与槽机制、动态属性、反射等功能。元对象系统使得 Qt 能够实现许多高级功能,如动态类型信息、对象树管理、事件处理等。
使用元对象系统需要注意:
●QObject 类是所有使用元对象系统的类的基类。
●必须在一个类的开头部分插入宏 Q_OBJECT,这样这个类才可以使用元对象系统的特性。
信号与槽机制
信号与槽机制是 Qt 的核心特性之一,用于对象之间的通信。信号是由对象发出的事件,槽是处理这些事件的函数。这一节仅作演示说明元对象系统支持信号槽机制,具体的信号和槽知识后续介绍。
定义信号与槽
在类中使用 signals 和 slots 关键字定义信号和槽:
切记元对象系统的类必须继承或间接继承自QObject,类的声明里包含Q_OBJECT
cpp
#include <QObject>
#include <QDebug>
class MyObject : public QObject
{
Q_OBJECT
public:
MyObject(QObject *parent = nullptr) : QObject(parent) {}
//定义信号
signals:
void mySignal(int value);
//定义槽函数
public slots:
void mySlot(int value) {
qDebug() << "Slot called with value:" << value;
}
};
连接信号与槽
使用QObject::connect 函数将信号连接到槽:
cpp
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
MyObject obj;
//连接信号和槽
QObject::connect(&obj, &MyObject::mySignal, &obj, &MyObject::mySlot);
//发送信号
emit obj.mySignal(42); // 发出信号
return app.exec();
}
上述代码输出
cpp
Slot called with value: 42
属性系统
对于元对象类,我们可以通过Qt的属性系统允许在运行时查询和操作对象的属性,这些操作在开发中用的不多,可以了解一下即可。
定义属性
使用 Q_PROPERTY 宏定义属性:
我们在MyObject类的声明中添加属性定义
cpp
Q_PROPERTY(int myProperty READ myProperty WRITE setMyProperty NOTIFY myPropertyChanged)
上面定义了几个属性:
- 定义了myProperty属性,为int类型成员变量
- 定义了myPropery属性 ,为READ函数类型
- 定义了setPropery属性, 为WRITE函数类型
- 定义了myPropertyChanged属性, 为NOTIFY类型,也就是信号类型
定义成员函数
接下来定义这些函数和成员, 函数名字需要和Q_PROPERTY中定义的一样
cpp
class MyObject : public QObject
{
Q_OBJECT
Q_PROPERTY(int myProperty READ myProperty WRITE setMyProperty NOTIFY myPropertyChanged)
public:
MyObject(QObject *parent = nullptr) : QObject(parent), m_myProperty(0) {}
//读函数,返回成员m_myPropery的值
int myProperty() const { return m_myProperty; }
//写函数, 设置m_myProperty成员
void setMyProperty(int value) {
if (m_myProperty != value) {
m_myProperty = value;
emit myPropertyChanged(m_myProperty);
}
}
signals:
//信号
void myPropertyChanged(int value);
private:
//成员变量
int m_myProperty;
};
属性访问和设置
可在主函数中设置属性值
cpp
MyObject obj;
//设置属性值为42
obj.setMyProperty(42);
因为我们的MyObject继承了QObject,且类的内部声明了Q_OBJECT, 所以可以将MyObject对象转化为元对象
cpp
//返回元对象
const QMetaObject *metaObject = obj.metaObject();
元对象可以根据属性名字返回属性所在的索引,因为我们之前定义的属性名为myProperty, 这里获取属性名myProperty所在的索引
cpp
//根据属性名字返回属性所在的索引
int propertyIndex = metaObject->indexOfProperty("myProperty");
我们可以根据属性索引获取具体的属性对象, 在打印这个属性对象的名字和数值
cpp
QMetaProperty metaProperty = metaObject->property(propertyIndex);
//分别打印属性
qDebug() << "Property name:" << metaProperty.name();
qDebug() << "Property value:" << metaProperty.read(&obj).toInt();
qDebug() << "Property value:" << obj.property(metaProperty.name()).toInt();
上面的代码输出
cpp
Property name: myProperty
Property value: 42
Property value: 42
动态属性
Qt 允许在运行时动态添加和获取属性:
cpp
obj.setProperty("dy_pro",1024);
qDebug() << "dynamic_property is " << obj.property("dy_pro").toInt();
上面的代码输出
cpp
dynamic_property is 1024
对象树管理
Qt 的对象树管理是通过 QObject 类的父子关系实现的。每个 QObject 对象可以有一个父对象和多个子对象,这种层次结构被称为对象树。对象树管理提供了一种方便的方式来管理对象的生命周期和层次结构。
核心概念
- 父对象和子对象:
-
- 每个
QObject对象可以有一个父对象和多个子对象。父对象负责管理子对象的生命周期,当父对象被销毁时,所有子对象也会被销毁。
- 每个
- 树状结构:
-
QObject对象的父子关系形成了一棵树。根对象没有父对象,所有其他对象都有一个唯一的父对象。
- 自动销毁:
-
- 当一个对象被销毁时,它的所有子对象也会被自动销毁。这种机制有助于避免内存泄漏。
我们可以构造如下图的对象树

代码演示
我们可以定义一个类TreeObj表示对象树的类
cpp
class TreeObj: public QObject
{
Q_OBJECT
public:
TreeObj(const QString& name, QObject* parent = nullptr)
: QObject(parent){
setObjectName(name);
}
~TreeObj(){
qDebug() << QString("call %1 destruction ").arg(objectName());
}
};
- setObjectName 为QObject的函数,主要用来设置对象名称。
- TreeObj构造函数接受一个字符串和父节点,字符串表示对象名称,父节点用来构造基类QObject。
我们写一个函数,构造一个父节点Parent,在父节点下面构造Child1和Child2,然后又在Child1下构造Grandchild1和Grandchild2.
cpp
void object_tree(){
auto parent = new TreeObj("Parent");
auto child1 = new TreeObj("Child1",parent);
auto child2 = new TreeObj("Child2",parent);
auto grand_child1 = new TreeObj("Grandchild1",child1);
auto grand_child2 = new TreeObj("Grandchild2",child1);
qDebug() << QString("parent has %1 children ")
.arg(parent->children().count());
qDebug() << QString("child1 has %1 children ")
.arg(child1->children().count());
}
上面的函数输出
cpp
"parent has 2 children "
"child1 has 2 children "
因为三个对象都是new出来的,按道理应该手动delete回收,但是有元对象系统,我们只需要delete最上层的父节点,他的所有子节点,以及子节点的孩子都会被回收
cpp
delete parent;
执行删除后,程序输出如下
cpp
"call Parent destruction "
"call Child1 destruction "
"call Grandchild1 destruction "
"call Grandchild2 destruction "
"call Child2 destruction "
可以看到所有子节点自动被回收了,因为对象树机制自动管理子节点的生命周期会随着父节点的结束而结束。
类型转换
qobject_cast 是 Qt 框架提供的一个类型转换函数,专门用于在 QObject 类及其派生类之间进行安全的类型转换。它类似于 C++ 标准库中的 dynamic_cast,但仅适用于 QObject 类型。
qobject_cast 使用 Qt 的元对象系统(Meta-Object System)来进行运行时检查(包括信号,属性等),以确保转换的安全性。如果转换失败,它会返回 nullptr。
cpp
void qobject_cast(){
BaseClass *base = new DerivedClass;
//基类指针转化为子类指针
DerivedClass *derived = qobject_cast<DerivedClass*>(base);
if (derived) {
qDebug() << "qobject_cast succeeded";
} else {
qDebug() << "qobject_cast failed";
}
delete base;
}
上面的代码会输出
cpp
"qobject_cast succeeded"
总结
学了这么多,上面的大家只做概念理解即可,不需要写出完整代码,面试时常问的两个问题
- 如何实现一个类,使其支持元对象系统(继承自QObject, 类的声明包含Q_OBJECT宏)
- 元对象系统有哪些特性?(信号和槽,属性系统,对象树管理,qobject_cast类型转换)