Qt五大核心特性之元对象系统

前言

Qt 的元对象系统(Meta-Object System)是 Qt 框架的核心之一,提供了一些 C++ 原生不具备的功能(因为在C++它们是静态的),如反射、信号槽机制、属性系统等。通过这个系统,Qt 实现了许多强大的功能,这使得它成为一个更易于使用和扩展的框架。

正文

元对象系统

1. 元对象系统的组成部分

1.1 Q_OBJECT 宏
  • Q_OBJECT 是元对象系统的入口。任何需要使用元对象系统功能的类都必须包含这个宏。
  • 它通常放在类的私有部分的顶部,并由 Qt 的元对象编译器(moc)处理,生成与类相关的元数据和代码。
cpp 复制代码
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() 来获取与对象相关的元对象。
cpp 复制代码
const QMetaObject *meta = myObject->metaObject();
qDebug() << "Class name:" << meta->className();
1.3 信号和槽(Signals and Slots)
  • 信号槽机制是 Qt 中的核心通信方式。信号(signal)是用来发出事件通知的,而槽(slot)是用来处理这些事件的。
  • signals:slots: 关键字标识了类中的信号和槽函数,信号槽的连接可以在编译时或运行时完成。
cpp 复制代码
QObject::connect(sender, &SenderClass::signalName, receiver, &ReceiverClass::slotName);
1.4 属性系统(Property System)
  • 属性系统使得可以通过字符串名称访问和操作对象的属性,这在 QML 和动画系统中尤其有用。
  • 使用 Q_PROPERTY 宏来定义属性。
cpp 复制代码
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() 来调用对象的槽函数或其它成员函数。
cpp 复制代码
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 通过元对象系统提供了一定程度的反射能力。这种能力主要体现在以下几个方面:

  1. 动态类型信息

    • 使用 QObject::metaObject() 可以在运行时获取与类相关的元数据(如类名、信号、槽、属性等)。
  2. 动态属性访问

    • 通过 QObject::setProperty()QObject::property() 方法,可以通过字符串名称在运行时访问和修改对象的属性。
  3. 信号与槽的动态连接

    • 使用 QObject::connect() 函数,可以在运行时通过字符串名称来动态连接信号和槽。这使得信号和槽的连接可以在运行时根据条件来建立或改变。
  4. 动态对象创建

    • 使用 QMetaObject::newInstance() 可以在运行时根据类的元对象创建新的对象实例(前提是类中有符合条件的构造函数)。

Qt 的元对象系统通过元对象编译器(moc)生成附加的代码,允许在运行时获取类的元数据,并使用这些元数据实现类似反射的功能。这种机制在实现动态特性、插件系统和QML绑定等功能时非常有用。

4. 元对象系统的使用

元对象系统的使用需要满足三个条件

  • 该类必须继承自QObject或者继承自继承QObject类的子类
  • 该类在声明Q_OBJECT这个宏时,必须在私有区域进行声明
  • 元对象编译器(moc)为每个QObject的子类,提供了实现员特性所必须的代码

MyClass.h

cpp 复制代码
#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

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

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 不能生成代码。

相关推荐
时光の尘5 小时前
C语言菜鸟入门·关键字·int的用法
c语言·开发语言·数据结构·c++·单片机·链表·c
C++忠实粉丝5 小时前
计算机网络socket编程(6)_TCP实网络编程现 Command_server
网络·c++·网络协议·tcp/ip·计算机网络·算法
禊月初三6 小时前
LeetCode 4.寻找两个中序数组的中位数
c++·算法·leetcode
程序员与背包客_CoderZ7 小时前
C++设计模式——Abstract Factory Pattern抽象工厂模式
c语言·开发语言·c++·设计模式·抽象工厂模式
fancc椰7 小时前
C++基础入门篇
开发语言·c++
晚安,cheems8 小时前
c++(入门)
开发语言·c++
人才程序员8 小时前
详解Qt QStorageInfo 存储信息类
c语言·开发语言·c++·后端·qt·界面
ZHOUPUYU8 小时前
最新‌VSCode保姆级安装教程(附安装包)
c语言·开发语言·c++·ide·windows·vscode·编辑器
kcwqxx9 小时前
day23|leetCode 39. 组合总和 , 40.组合总和II , 131.分割回文串
c++·算法·leetcode
机器视觉知识推荐、就业指导9 小时前
基于Qt/C++/Opencv实现的一个视频中二维码解析软件
c++·qt·opencv