← 返回博客列表

一、元对象系统概述

Qt 的元对象系统(Meta-Object System)是 Qt 框架的基石,它赋予了 Qt 超越标准 C++ 的运行时能力。这套系统并非 C++ 标准的一部分,而是 Qt 通过 moc(Meta-Object Compiler) 编译器和一系列宏(Q_OBJECT、Q_PROPERTY、SIGNAL/SLOT 等)在编译期生成额外代码来实现的。

核心能力
① 信号与槽机制(跨线程、跨进程通信)
② 属性系统(Q_PROPERTY 动态属性读写)
③ 运行时反射(对象自省、方法查询)
④ 动态方法调用(invokeMethod 字符串调起)
⑤ 事件系统(基于信号槽的事件派发)
⑥ 对象树与内存管理(父子对象自动析构)
⑦ 翻译系统(tr() 多语言支持)

1.1 系统架构图

QObject
+ metaObject() : const QMetaObject*
+ connect() : bool
+ property() : QVariant
+ setProperty() : bool
# d_ptr : QObjectPrivate*
——→
QMetaObject
- d : QMetaObjectPrivate*
+ className() : const char*
+ methodCount() : int
+ propertyCount() : int
+ invoke() : int
- m_staticMetaObject : QMetaObject
——→
moc_xxx.cpp
+ qt_metacall()
+ qt_metacast()
+ staticMetaObject
+ 元数据字符串表

1.2 工作流程

源文件在构建时被 moc 解析、生成 moc_*.cpp,再与用户代码一起编译。运行时通过 QMetaObject 提供反射能力,QObject 的每个子类都持有一个指向该元对象的指针。

二、moc 编译器:代码生成的幕后功臣

2.1 moc 是什么

moc(Meta-Object Compiler)是 Qt 构建体系的核心工具,负责解析 C++ 源文件中带 Q_OBJECT 宏的类定义,并自动生成实现元对象功能的 C++ 代码(moc_*.cpp)。它本质上是一个预编译器,在 C++ 编译之前运行。

moc 的输出
· 每个 Q_OBJECT 类:生成 moc_ClassName.cpp
· 每个 Q_GADGET 类:同样生成 moc 文件
· 编译后的元对象数据作为静态变量存入 .rodata 段

2.2 moc 生成的内容

moc_Widget.cpp 生成内容
① Widget::staticMetaObject — 元对象描述
② Widget::qt_metacall() — 方法调度入口
③ Widget::qt_metacast() — 类型转换
④ 信号实现代码(emit 展开)
⑤ 属性存取辅助函数
⑥ 字符串表(方法名、属性名)

2.3 构建集成

Qt 通过两种构建系统集成 moc:

qmake 方式:

# .pro 文件
QT += core gui
TARGET = MyApp

# qmake 自动识别含 Q_OBJECT 的头文件
# 自动调用 moc 生成代码
HEADERS += widget.h
SOURCES += main.cpp widget.cpp

CMake 方式:

# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(MyApp)

find_package(Qt5 REQUIRED COMPONENTS Core Gui)

# AUTOMOC 自动处理 moc
set(CMAKE_AUTOMOC ON)

add_executable(MyApp main.cpp widget.cpp)
target_link_libraries(MyApp Qt5::Core Qt5::Gui)

2.4 手动调用 moc

# 直接调用 moc
moc widget.h -o moc_widget.cpp

# 带选项
moc -f widget.h -o moc_widget.cpp     # -f 强制生成 include
moc -DWIDGET_H widget.h -o moc_widget.cpp  # 指定头文件保护宏

三、Q_OBJECT 宏:元对象的身份证

3.1 Q_OBJECT 宏定义

Q_OBJECT 宏展开后,为类添加了大量成员函数和静态变量声明:

# Q_OBJECT 宏展开后的等价代码(简化)
class Widget : public QObject {
    Q_OBJECT
public:
    // 元对象访问
    static const QMetaObject staticMetaObject;
    virtual const QMetaObject *metaObject() const;

    // 元方法调度
    virtual int qt_metacall(QMetaObject::Call c, int id, void **a);

    // 元类型转换
    virtual void *qt_metacast(const char *clname);

    // 信号声明展开
    void objectNameChanged();

    // 属性存取
    void setObjectName(const QString &name);
    QString objectName() const;
};

3.2 Q_OBJECT 的必要条件

必须继承自 QObject,且 moc 必须能看到类定义的完整声明(通常在头文件中)。
Q_OBJECT 宏不能用于模板类、命名空间内的类或匿名命名空间的类。

3.3 Q_GADGET 宏

对于不想继承 QObject 但又需要元对象能力的类,Qt 提供了 Q_GADGET 宏:

struct MyStruct {
    Q_GADGET       // 生成 staticMetaObject
    Q_PROPERTY(QString name READ name WRITE setName)
    Q_PROPERTY(int value READ value WRITE setValue)

public:
    QString name() const { return m_name; }
    void setName(const QString &n) { m_name = n; }
    int value() const { return m_value; }
    void setValue(int v) { m_value = v; }

private:
    QString m_name;
    int m_value = 0;
};

Q_GADGET 的特点:

四、信号槽机制

4.1 信号槽的本质

信号槽是 Qt 元对象系统最著名的应用。当对象状态变化时,通过 emit 信号通知感兴趣的对象,接收方通过 槽函数接收通知并响应。信号槽机制的底层完全由元对象系统驱动。

4.2 基本用法

class Sender : public QObject {
    Q_OBJECT
public:
    void trigger() {
        emit valueChanged(42);
    }
signals:
    void valueChanged(int value);
};

class Receiver : public QObject {
    Q_OBJECT
public slots:
    void onValueChanged(int v) {
        qDebug() « "收到值:" « v;
    }
};

// 连接
Sender *sender = new Sender;
Receiver *receiver = new Receiver;
connect(sender, &Sender::valueChanged, receiver, &Receiver::onValueChanged);

// 触发
sender->trigger();  // 输出: 收到值: 42

4.3 连接语法演进

语法 示例 特点
旧式 SIGNAL(valueChanged(int)) 字符串匹配,运行时检查
新式 (函数指针) &Sender::valueChanged 编译期类型检查
Lambda [](int v){...} 灵活匿名函数

4.4 信号槽的底层实现

连接阶段:connect() 通过元对象系统查找信号和槽的索引,将连接信息存入 QObjectConnection 链表。

发送阶段:emit 展开为调用 moc 生成的信号实现,该实现遍历连接链表,通过 qt_metacall() 调用各槽函数。

调度方式:根据连接类型(DirectConnection / QueuedConnection)决定同步或异步调用。

4.5 连接类型详解

#include <QMetaObject>

// 1. DirectConnection: 同步调用,立即执行
connect(sender, &Sender::valueChanged, receiver, &Receiver::onValueChanged,
        Qt::DirectConnection);

// 2. QueuedConnection: 异步调用,事件循环处理
connect(sender, &Sender::valueChanged, receiver, &Receiver::onValueChanged,
        Qt::QueuedConnection);

// 3. AutoConnection: 默认,同线程 Direct,跨线程 Queued
connect(sender, &Sender::valueChanged, receiver, &Receiver::onValueChanged,
        Qt::AutoConnection);

// 4. BlockingQueuedConnection: 阻塞跨线程调用
connect(sender, &Sender::valueChanged, receiver, &Receiver::onValueChanged,
        Qt::BlockingQueuedConnection);

// 5. UniqueConnection: 防止重复连接
connect(sender, &Sender::valueChanged, receiver, &Receiver::onValueChanged,
        Qt::UniqueConnection);

// 6. SingleShotConnection: 只触发一次
connect(sender, &Sender::valueChanged, receiver, &Receiver::onValueChanged,
        Qt::SingleShotConnection);

4.6 跨线程信号槽

class Worker : public QObject {
    Q_OBJECT
public slots:
    void doWork() {
        int result = 42;
        emit finished(result);
    }
signals:
    void finished(int result);
};

// 跨线程:Worker 在子线程执行,通知主线程
QThread *thread = new QThread;
Worker *worker = new Worker;
worker->moveToThread(thread);

connect(thread, &QThread::started, worker, &Worker::doWork);
connect(worker, &Worker::finished, this, &MainWindow::onFinished);
connect(worker, &Worker::finished, thread, &QThread::quit);

五、属性系统:Q_PROPERTY

5.1 Q_PROPERTY 声明

Q_PROPERTY(type name
    [READ getFunction | MEMBER memberName [NEEDS REVISION]]
    [WRITE setFunction]
    [RESET resetFunction]
    [NOTIFY notifySignal]
    [REVISION int]
    [DESIGNABLE bool]
    [SCRIPTABLE bool]
    [STORED bool]
    [EDITABLE bool]
    [PERSISTENT bool]
    [USER bool]
    [CONSTANT]
    [FINAL])

5.2 属性系统实战

class RobotArm : public QObject {
    Q_OBJECT
    Q_PROPERTY(double angle READ angle WRITE setAngle NOTIFY angleChanged)
    Q_PROPERTY(int axisCount READ axisCount CONSTANT)
    Q_PROPERTY(QString model READ model WRITE setModel DESIGNABLE true)

public:
    double angle() const { return m_angle; }
    void setAngle(double a) {
        if (qFuzzyCompare(m_angle, a)) return;
        m_angle = a;
        emit angleChanged(a);
    }
    int axisCount() const { return 6; }
    QString model() const { return m_model; }
    void setModel(const QString &m) { m_model = m; }

signals:
    void angleChanged(double angle);

private:
    double m_angle = 0.0;
    QString m_model = "IRB-6700";
};

5.3 属性的动态读写

RobotArm arm;

// 读取属性
QVariant val = arm.property("angle");
double angle = val.toDouble();

// 写入属性
arm.setProperty("angle", 90.0);

// 查询元对象中的属性
const QMetaObject *meta = arm.metaObject();
for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) {
    QMetaProperty prop = meta->property(i);
    qDebug() « prop.name() « prop.type();
}

5.4 属性在 Qt Designer 中的集成

Q_PROPERTY 声明的属性会自动出现在 Qt Designer 的属性编辑器中,DESIGNABLE 控制是否可编辑,STORED 控制是否参与持久化,USER 控制是否为默认属性。

六、动态属性

6.1 动态属性的创建

除了编译期通过 Q_PROPERTY 声明的静态属性,QObject 还支持运行时动态添加属性

QObject *obj = new QObject;

// 设置动态属性
obj->setProperty("customColor", QColor("red"));
obj->setProperty("userLevel", 3);

// 读取动态属性
QColor color = obj->property("customColor").value();
int level = obj->property("userLevel").toInt();

// 查询是否存在
bool has = obj->property("customColor").isValid();

// 移除动态属性(设为无效)
obj->setProperty("customColor", QVariant());

6.2 动态属性的存储原理

内部实现:每个 QObject 内部维护一个 QHash<QByteArray, QVariant> 存储动态属性。动态属性与静态属性查询时,先查动态属性表,再查元对象中的静态属性。

性能注意:动态属性基于哈希表,O(1) 读写,但类型安全由调用者保证,编译期无法检查。

七、元方法调用:invokeMethod

7.1 invokeMethod 基本用法

QMetaObject::invokeMethod(obj, "methodName",
    Qt::DirectConnection,
    Q_ARG(Type1, arg1),
    Q_ARG(Type2, arg2),
    Q_RETURN(ReturnType, retVal));

7.2 实战示例

class Calculator : public QObject {
    Q_OBJECT
public slots:
    int add(int a, int b) { return a + b; }
    QString greet(const QString &name) {
        return QString("Hello, %1!").arg(name);
    }
};

Calculator calc;

// 无返回值调用
QMetaObject::invokeMethod(&calc, "add",
    Qt::DirectConnection,
    Q_ARG(int, 3),
    Q_ARG(int, 4));

// 有返回值调用
int result = 0;
QMetaObject::invokeMethod(&calc, "add",
    Qt::DirectConnection,
    Q_RETURN(int, result),
    Q_ARG(int, 3),
    Q_ARG(int, 4));
qDebug() « result;  // 7

// 自定义类型参数
QString greeting;
QMetaObject::invokeMethod(&calc, "greet",
    Qt::DirectConnection,
    Q_RETURN(QString, greeting),
    Q_ARG(QString, "World"));

7.3 Q_ARG 与 Q_RETURN

Q_ARG 和 Q_RETURN 是宏,用于封装参数类型和值。它们在编译期生成类型信息,运行时通过元对象系统匹配方法签名。

必须使用 Q_DECLARE_METATYPE 注册自定义类型,才能在 invokeMethod 中使用:
Q_DECLARE_METATYPE(MyCustomType)

// 跨线程使用时还需 qRegisterMetaType
qRegisterMetaType<MyCustomType>("MyCustomType");

7.4 异步调用

// 在对象所在线程执行
QMetaObject::invokeMethod(obj, "doWork",
    Qt::QueuedConnection);

// 指定延迟调用
QTimer::singleShot(1000, obj, "process");

八、运行时自省

8.1 类名查询

RobotArm arm;
const QMetaObject *meta = arm.metaObject();
qDebug() « meta->className();  // "RobotArm"

8.2 方法枚举

const QMetaObject *meta = arm.metaObject();

// 遍历所有方法
for (int i = meta->methodOffset(); i < meta->methodCount(); ++i) {
    QMetaMethod method = meta->method(i);
    qDebug() « method.name() « method.methodType();
}

// 查找特定方法
QMetaMethod m = meta->method(meta->indexOfMethod("setAngle(double)"));

8.3 属性枚举

const QMetaObject *meta = arm.metaObject();

for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) {
    QMetaProperty prop = meta->property(i);
    qDebug() « prop.name()
              « prop.type()
              « prop.isReadable()
              « prop.isWritable();
}

8.4 类型信息查询

QMetaType::Type t = QMetaType::fromType();
qDebug() « t.name();        // "QString"
qDebug() « t.isValid();    // true
qDebug() « t.id();         // QMetaType::QString

// 自定义类型
Q_DECLARE_METATYPE(MyType)
int typeId = qRegisterMetaType("MyType");

8.5 继承关系查询

// 向上遍历父类元对象
const QMetaObject *meta = arm.metaObject();
while (meta) {
    qDebug() « meta->className();
    meta = meta->superClass();
}
// RobotArm -> QObject

九、moc 生成代码深度剖析

9.1 示例类定义

class Demo : public QObject {
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
    Q_PROPERTY(int count READ count)

public:
    Demo() : m_count(0) {}
    QString name() const { return m_name; }
    void setName(const QString &n) {
        m_name = n;
        emit nameChanged(n);
    }
    int count() const { return m_count; }
    Q_INVOKABLE void reset() { m_count = 0; }

signals:
    void nameChanged(const QString &name);

private:
    QString m_name;
    int m_count;
};

9.2 moc 生成的关键代码

// moc_Demo.cpp 生成内容(简化)

// 1. 字符串表:所有方法名、属性名集中存储
static const char *qt_meta_stringdata_Demo_stringdata[] = {
    "Demo",
    "QObject",
    "nameChanged",
    "name",
    "count",
    "reset()",
    "setName(QString)",
    "name()",
    "count()",
    "0",
    "1",
};

// 2. 元对象数据
const QMetaObject Demo::staticMetaObject = {
    { &QObject::staticMetaObject, qt_meta_stringdata_Demo_stringdata, 11, 8, 3 },
    // 方法表:索引0-2为信号/槽,3-7为方法
    qt_meta_method_Demo,
    // 属性表:索引0-1为属性
    qt_meta_property_Demo,
    // 构造函数表
    qt_meta_constructor_Demo,
    nullptr,
    // 类标志
    { 0, 0, 0, 0, nullptr, nullptr, nullptr }
};

// 3. qt_metacast:类型转换
void *Demo::qt_metacast(const char *_clname) {
    if (!strcmp(_clname, qt_meta_stringdata_Demo_stringdata[0]))
        return static_cast<void*>(const_cast<Demo*>(this));
    if (!strcmp(_clname, qt_meta_stringdata_Demo_stringdata[1]))
        return static_cast<QObject*>(const_cast<Demo*>(this));
    return nullptr;
}

// 4. qt_metacall:方法调度
int Demo::qt_metacall(QMetaObject::Call _c, int _id, void **_a) {
    _id = QObject::qt_metacall(_c, _id, _a);
    if (_id < 0)
        return _id;
    if (_c == QMetaObject::InvokeMetaMethod) {
        if (_id < 3) {
            switch (_id) {
            case 0:  // signal: nameChanged
                QMetaObject::activate(this, &staticMetaObject, _id, _a);
                break;
            case 1:  // slot: setName
                setName((*const QString*)(_a[1]));
                break;
            case 2:  // method: reset
                reset();
                break;
            }
        }
        return _id - 3;
    }
    if (_c == QMetaObject::ReadProperty) {
        switch (_id) {
        case 0:  // property: name
            *(*void**)(_a[0]) = &m_name;
            break;
        case 1:  // property: count
            *(int*)(_a[0]) = m_count;
            break;
        }
    }
    if (_c == QMetaObject::WriteProperty) {
        switch (_id) {
        case 0:  // property: name
            setName(*(*const QString**)(_a[0]));
            break;
        }
    }
    return -1;
}

9.3 信号的展开

// 原始信号声明
void Demo::nameChanged(const QString &name)
{
    // 以下代码由 moc 生成
    void *_a[] = { nullptr, const_cast<void*>(const_cast<QString*>(&name)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

9.4 moc 生成流程

源文件 (.h/.cpp)
class Demo : QObject
Q_OBJECT
Q_PROPERTY(...)
signals / slots
——→
moc 解析
识别 Q_OBJECT 宏
解析属性/信号/槽
构建元数据结构
——→
moc_Demo.cpp
静态元对象
字符串表
qt_metacall
信号实现
——→
编译链接
与用户代码一起编译
运行时通过 QMetaObject
提供反射能力

十、高级用法与实战技巧

10.1 自定义类型注册

// 1. 声明类型(通常在头文件中)
struct Point { double x, y; };
Q_DECLARE_METATYPE(Point)

// 2. 注册类型(main 或插件初始化)
int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    qRegisterMetaType<Point>("Point");
    return app.exec();
}

// 3. 在信号槽中使用
connect(sender, &Sender::pointChanged, receiver, &Receiver::onPoint);

// 4. QVariant 转换
QVariant v;
v.setValue(Point{1.0, 2.0});
Point p = v.value();

10.2 Q_INVOKABLE 与通用接口

class PluginInterface : public QObject {
    Q_OBJECT
public:
    explicit PluginInterface(QObject *parent = nullptr) : QObject(parent) {}

    // 通用插件方法接口
    Q_INVOKABLE QVariantMap execute(const QString &method,
                                    const QVariantMap &params) {
        QVariantMap result;
        const QMetaObject *meta = metaObject();
        int idx = meta->indexOfMethod(method + "(" + paramTypes(method) + ")");
        if (idx < 0) return {};
        QMetaMethod metaMethod = meta->method(idx);
        return metaMethod.invoke(this,
            Qt::DirectConnection,
            Q_RETURN_ARG(QVariantMap, result),
            Q_ARG(QVariantMap, params));
    }
};

10.3 动态属性实现灵活配置

// 配置系统示例:动态属性实现通用配置
class ConfigManager : public QObject {
    Q_OBJECT
public:
    void loadFromJson(const QString &filePath) {
        QFile file(filePath);
        if (!file.open(QIODevice::ReadOnly)) return;
        QJsonObject root = QJsonDocument::fromJson(file.readAll()).object();
        for (auto it = root.constBegin(); it != root.constEnd(); ++it) {
            setProperty(it.key().toUtf8(), it.value().toVariant());
        }
    }

    QVariant get(const QString &key) const {
        return property(key.toUtf8());
    }
};

10.4 元对象驱动的序列化

// 利用元对象自动序列化属性
QJsonObject objectToJson(const QObject *obj) {
    QJsonObject json;
    const QMetaObject *meta = obj->metaObject();

    for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) {
        QMetaProperty prop = meta->property(i);
        if (prop.isReadable() && prop.isStored()) {
            json[QString::fromUtf8(prop.name())] = prop.read(obj).toJsonValue();
        }
    }
    return json;
}

// 反序列化
void jsonToObject(const QJsonObject &json, QObject *obj) {
    const QMetaObject *meta = obj->metaObject();
    for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) {
        QMetaProperty prop = meta->property(i);
        QString key = QString::fromUtf8(prop.name());
        if (json.contains(key) && prop.isWritable()) {
            prop.write(obj, json[key].toVariant());
        }
    }
}

10.5 自定义 Q_PROPERTY 类型

// 声明自定义类型的元类型转换器
Q_DECLARE_METATYPE(MyColor)

// 实现类型转换器
class MyColorConverter : public QMetaType::TypeConverter {
    MyColorConverter() : QMetaType::TypeConverter(qMetaTypeId()) {}
    bool fromVariant(const QVariant &v, MyColor *out) override {
        if (v.canConvert<QString>()) {
            *out = MyColor::fromHex(v.toString());
            return true;
        }
        return false;
    }
    QVariant toVariant(const MyColor &v) override {
        return QVariant(v.toHex());
    }
};

// 注册转换器
void registerMyColor() {
    QMetaType::registerConverter(&MyColorConverter::fromVariant);
    QMetaType::registerConverter([](const QString &s) {
        return MyColor::fromHex(s);
    });
}

十一、与 C++ RTTI 的对比

特性 C++ RTTI Qt 元对象系统
实现机制 编译器原生支持 moc 编译期生成代码
类型识别 typeid / dynamic_cast metaObject()->className()
反射能力 仅类型识别 完整反射(方法/属性/枚举)
动态调用 不支持 invokeMethod / 信号槽
属性系统 不支持 Q_PROPERTY 声明式属性
跨线程 不支持 QueuedConnection 原生支持
多语言 不支持 tr() 翻译集成
性能开销 极小 运行时查表,有一定开销
适用范围 所有 C++ 类 仅 QObject 子类 / Q_GADGET
总结:C++ RTTI 仅提供最基础的运行时类型识别能力,而 Qt 元对象系统提供了完整的动态反射框架,包括方法调用、属性操作、信号槽通信、跨线程调度等高级特性。在需要运行时多态、序列化、脚本绑定等场景下,Qt 元对象系统的能力远超 RTTI。

十二、总结

Qt 元对象系统是理解 Qt 框架的核心钥匙。它的设计哲学是编译期代码生成 + 运行时元数据,通过 moc 编译器在构建时生成额外代码,赋予 QObject 子类远超标准 C++ 的运行时能力。

核心要点回顾

实战建议

① 优先使用新式连接语法:编译期类型检查,更安全

② 合理使用动态属性:运行时灵活配置,但注意性能

③ 自定义类型务必注册:Q_DECLARE_METATYPE + qRegisterMetaType

④ 善用元对象序列化:基于 Q_PROPERTY 实现通用 JSON 序列化

⑤ 理解 moc 生成流程:排查问题时查看 moc 生成的代码

⑥ 合理使用 Q_GADGET:值类型场景避免不必要的 QObject 继承