核心功能基础
# 01.核心功能基础
# 目录介绍
# 1.1 基础类概念
Qt Core 是 Qt 框架的基础模块,提供了跨平台的 C++ 核心功能,不依赖于 GUI 组件。
Qt 的对象模型和元对象系统是 Qt 框架的核心特性之一,它们为 Qt 提供了强大的功能,如信号与槽机制、动态属性、反射等。
# 1.1.1 对象模型
功能:提供对象树管理、父子关系、对象生命周期管理。核心类:
QObject:支持对象树和父子关系。所有 Qt 对象的基类,提供信号槽机制、对象树管理、事件处理等核心功能。QObject::parent()、QObject::children():访问父对象和子对象。
# 1.1.2 元对象系统
功能:提供运行时类型信息(RTTI)、信号与槽机制、动态属性系统等。核心类:
QObject:所有 Qt 对象的基类。QMetaObject:存储类的元信息(如类名、信号、槽、属性等)。QMetaMethod、QMetaProperty:用于访问方法和属性信息。- Q_GADGET:为非 QObject 类添加元对象能力
- Q_PROPERTY:声明属性系统
- Q_INVOKABLE:使方法可通过元对象系统调用
元对象系统,核心概念
- Qt 使用元对象系统实现信号与槽、动态属性、反射等功能。元对象系统通过 moc(元对象编译器)生成额外的代码,扩展 C++ 的功能。
关键特性
- Q_OBJECT 宏:在类声明中使用 Q_OBJECT 宏,启用元对象系统功能。
- 动态属性:可以在运行时为对象添加属性。
- 反射:可以在运行时获取对象的类名、方法、属性等信息。
# 1.1.3 看个案例
以下是一个简单的示例,展示了 Qt 对象模型和元对象系统的使用:
#include <QObject>
#include <QDebug>
class MyClass : public QObject {
Q_OBJECT // 启用元对象系统
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged) //属性
public:
explicit MyClass(QObject *parent = nullptr) : QObject(parent), m_value(0) {} //构造方法
int value() const { return m_value; }
void setValue(int value) {
if (m_value != value) {
m_value = value;
emit valueChanged(m_value); //发送信号
}
}
signals: //信号
void valueChanged(int value);
private:
int m_value;
};
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
MyClass obj;
qDebug() << obj.metaObject()->className(); // 输出 "MyClass"
obj.setObjectName("MyObject");
// 使用元对象系统动态调用槽函数
QMetaObject::invokeMethod(&obj, "setValue", Q_ARG(int, 42));
// 使用反射获取属性
qDebug() << "Value:" << obj.property("value").toInt();
return app.exec();
}
#include "main.moc"
# 1.2 QObject对象
# 1.2.1 QObject说明
QObject 是 Qt 框架中最基础的类,几乎所有 Qt 类都直接或间接继承自 QObject。
它提供了 Qt 对象模型的核心功能,如对象树管理、信号与槽机制、事件处理、动态属性等。
# 1.2.2 树与父子关系
对象树与父子关系:Qt 对象可以组织成树形结构,每个对象可以有一个父对象和多个子对象。
当父对象被销毁时,其所有子对象也会自动销毁。通过 QObject::setParent() 设置父对象,或通过 QObject::children() 获取子对象。
void setParent(QObject *parent);
QObject *parent() const;
# 1.2.3 对象名称与查找
每个 QObject 对象可以有一个唯一的名称(objectName),用于在对象树中查找。
使用 QObject::findChild() 或 QObject::findChildren() 查找对象。
QObject *findChild(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const;
QList<QObject *> findChildren(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const;
# 1.2.4 动态属性
Qt 对象可以动态添加属性,这些属性可以在运行时查询和修改。使用 QObject::setProperty() 设置属性,QObject::property() 获取属性。
bool setProperty(const char *name, const QVariant &value);
QVariant property(const char *name) const;
# 1.2.5 信号与槽
Qt 的信号与槽机制是对象模型的核心特性,用于对象之间的通信。
信号是事件的通知,槽是事件的响应函数。使用 QObject::connect() 连接信号与槽。
static bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection);
static bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method);
# 1.2.6 事件处理
QObject 提供了事件处理机制,可以重写 QObject::event() 或特定事件处理函数(如 QObject::timerEvent())来处理事件。
virtual bool event(QEvent *event);
virtual void timerEvent(QTimerEvent *event);
# 1.2.7 线程支持
QObject 可以在多线程中使用,但需要注意线程安全性。使用 QObject::moveToThread() 将对象移动到另一个线程。
void moveToThread(QThread *targetThread);
# 1.2.8 注意事项
Q_OBJECT宏: 所有使用信号与槽、动态属性等特性的类必须包含Q_OBJECT宏。- 对象树管理: 使用对象树管理父子关系,避免内存泄漏。注意不要在栈上创建带有父对象的
QObject,因为父对象销毁时会尝试删除子对象。 - 线程安全:
QObject默认是线程不安全的,跨线程操作时需要使用QMetaObject::invokeMethod()或信号与槽。 - 信号与槽: 信号与槽的参数类型必须完全匹配,否则连接会失败。使用
Qt::QueuedConnection实现跨线程的信号与槽连接。
# 1.3 元对象系统
元对象系统(Meta-Object System)是 Qt 实现对象模型的基础,它提供了以下功能:
# 1.3.1 元对象说明
每个 QObject 派生类都有一个对应的元对象,用于描述类的元信息(如类名、信号、槽、属性等)。
元对象由 moc(Meta-Object Compiler)生成,存储在 moc_*.cpp 文件中。
# 1.3.2 元对象编译器
moc 是 Qt 的工具,用于处理 Q_OBJECT 宏,生成元对象代码。在编译 Qt 项目时,moc 会自动处理包含 Q_OBJECT 宏的头文件。
# 1.3.3 反射(Reflection)
元对象系统支持反射,可以在运行时查询对象的类信息、信号、槽、属性等。使用 QMetaObject 类访问元对象信息。
# 1.3.4 动态调用
通过元对象系统,可以在运行时动态调用对象的信号、槽和属性。使用 QMetaObject::invokeMethod() 动态调用槽函数。
QMetaObject::invokeMethod 它通常用于跨线程调用、延迟执行或调用信号和槽。
函数原型
static bool QMetaObject::invokeMethod(
QObject *object, // 目标对象
const char *method, // 方法名
Qt::ConnectionType type, // 连接类型
QGenericReturnArgument ret, // 返回值
QGenericArgument val0 = QGenericArgument(), // 参数 1
QGenericArgument val1 = QGenericArgument(), // 参数 2
QGenericArgument val2 = QGenericArgument(), // 参数 3
QGenericArgument val3 = QGenericArgument(), // 参数 4
QGenericArgument val4 = QGenericArgument(), // 参数 5
QGenericArgument val5 = QGenericArgument(), // 参数 6
QGenericArgument val6 = QGenericArgument(), // 参数 7
QGenericArgument val7 = QGenericArgument(), // 参数 8
QGenericArgument val8 = QGenericArgument(), // 参数 9
QGenericArgument val9 = QGenericArgument() // 参数 10
);
参数说明
object:目标对象,即调用方法的对象。method:方法名,可以是信号、槽或普通成员函数。方法名需要以字符串形式提供,例如"methodName"。type:连接类型,决定方法调用的方式。常用值:
Qt::AutoConnection:自动选择(默认)。Qt::DirectConnection:直接调用(同步)。Qt::QueuedConnection:队列调用(异步)。
ret:返回值,使用Q_RETURN_ARG宏定义。val0到val9:方法参数,使用Q_ARG宏定义。
看一个案例,比如:
QMetaObject::invokeMethod(this, [=]() { DoOnPalmServiceWorkingStatus(palm_working_enable); }, Qt::QueuedConnection);
- QMetaObject::invokeMethod:Qt 框架提供的函数,用于在对象上调用指定的方法。
- this:当前对象的指针,表示调用方法的对象。
- = { DoOnPalmServiceWorkingStatus(palm_working_enable); }:Lambda 函数,捕获当前作用域的变量(palm_working_enable),并调用 DoOnPalmServiceWorkingStatus 方法。
- Qt::QueuedConnection:指定调用方式为队列连接(异步调用),即方法会在事件循环中稍后执行,而不是立即执行。
# 1.4 关键宏与类
# 1.4.1 Q_OBJECT
Q_OBJECT 是一个宏,用于启用 Qt 的元对象系统功能,包括信号与槽机制、动态属性、对象树管理等。所有使用元对象系统的类必须包含 Q_OBJECT 宏。该宏会触发 moc 生成元对象代码。
- 启用信号与槽机制。
- 支持动态属性(
Q_PROPERTY)。 - 支持元对象信息(如
metaObject())。 - 支持对象树管理(如
parent()和children())。
注意事项
- 使用
Q_OBJECT的类必须继承自QObject。 - 如果使用
Q_OBJECT,必须运行moc(元对象编译器)生成额外的代码。
# 1.4.2 QMetaObject
QMetaObject 是 Qt 元对象系统的核心类,提供了对类元信息的访问。它的作用:
- 获取类的名称、父类、信号、槽、属性等信息。
- 动态调用方法或访问属性。
#include <QObject>
#include <QMetaObject>
#include <QDebug>
class MyClass : public QObject {
Q_OBJECT
public:
MyClass(QObject *parent = nullptr) : QObject(parent) {}
void doSomething() {
qDebug() << "Doing something...";
}
};
int main() {
MyClass obj;
// 获取元对象
const QMetaObject *meta = obj.metaObject();
// 获取类名
qDebug() << "Class name:" << meta->className();
// 获取父类名
qDebug() << "Parent class name:" << meta->superClass()->className();
return 0;
}
# 1.4.3 Q_PROPERTY
Q_PROPERTY 是一个宏,用于声明类的属性,支持属性绑定、动画、序列化等功能。
作用
- 声明一个属性,并指定其读写方法、通知信号等。
- 支持在 QML 中绑定属性。
用法
#include <QObject>
#include <QDebug>
class MyClass : public QObject {
Q_OBJECT
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged) // 声明属性
public:
MyClass(QObject *parent = nullptr) : QObject(parent), m_value(0) {}
int value() const { return m_value; } // 读方法
void setValue(int value) { // 写方法
if (m_value != value) {
m_value = value;
emit valueChanged(); // 发射通知信号
}
}
signals:
void valueChanged(); // 通知信号
private:
int m_value;
};
int main() {
MyClass obj;
// 使用属性
obj.setValue(42);
qDebug() << "Value:" << obj.value();
return 0;
}
属性选项
READ:指定读方法。WRITE:指定写方法。NOTIFY:指定属性变化时的通知信号。RESET:指定重置方法。MEMBER:指定成员变量。CONSTANT:声明为常量属性。FINAL:禁止子类重写属性。
# 1.4.4 Q_INVOKABLE
Q_INVOKABLE 是一个宏,用于将方法标记为可通过元对象系统调用的方法。
作用:允许通过 QMetaObject::invokeMethod() 动态调用方法。支持在 QML 中调用方法。
1.跨线程调用,在 Qt 中,如果需要在非主线程中调用主线程的对象方法,可以使用 QMetaObject::invokeMethod 并指定 Qt::QueuedConnection。
QMetaObject::invokeMethod(
targetObject, // 目标对象
"methodName", // 方法名
Qt::QueuedConnection // 异步调用
);
2.延迟调用,通过 Qt::QueuedConnection,可以实现延迟调用。
QMetaObject::invokeMethod(
targetObject,
"methodName",
Qt::QueuedConnection
);
3.调用带参数的方法,如果方法需要参数,可以使用 Q_ARG 宏传递参数。
QMetaObject::invokeMethod(
targetObject,
"setValue",
Qt::AutoConnection,
Q_ARG(int, 42), // 参数 1
Q_ARG(QString, "Hello") // 参数 2
);
4.调用带返回值的方法,如果方法有返回值,可以使用 Q_RETURN_ARG 宏。
int result;
QMetaObject::invokeMethod(
targetObject,
"calculate",
Qt::DirectConnection,
Q_RETURN_ARG(int, result), // 返回值
Q_ARG(int, 10), // 参数 1
Q_ARG(int, 20) // 参数 2
);
qDebug() << "Result:" << result;
注意事项
Q_INVOKABLE方法可以是任意返回类型和参数类型。- 如果方法有返回值,可以使用
Q_RETURN_ARG获取返回值。
# 1.4.5 总结一下
Q_OBJECT:启用元对象系统功能。QMetaObject:提供对类元信息的访问。Q_PROPERTY:声明属性,支持绑定和通知。Q_INVOKABLE:标记方法为可通过元对象系统调用。
# 1.X QObject对象树与内存管理
QObject的对象树是Qt核心内存机制——理解析构顺序才能避免野指针。
# 父对象析构顺序
一个父对象析构时,按子对象加入的逆序递归删除:
parent
├── child1 ← 最晚删除
├── child2
└── child3 ← 最先删除
关键规则:
- 父对象析构自动删除所有子对象——不需要手动管理
- 手动delete子对象安全——会自动从父对象children中移除
deleteLater()不立即析构——事件循环下个迭代才删除,跨线程安全
# 与QML的所有权
// C++创建→C++管理
auto* backend = new Backend();
engine.rootContext()->setContextProperty("backend", backend);
// QML销毁时不会delete backend——CppOwnership
// QML创建→JS GC管理
// QML: Backend { id: qmlB }
// C++不要delete qmlB
QQmlEngine::setObjectOwnership(obj, QQmlEngine::CppOwnership);
# 1.Y QVariant类型系统
QVariant是Qt的"通用类型容器"——内部用union+type tag:
QVariant v;
v = 42; // typeId=Int, 存qint32
v = QString("hello"); // typeId=String, 存QString(COW)
// 自定义类型注册
struct SensorData { float temp; int press; };
Q_DECLARE_METATYPE(SensorData)
QVariant v = QVariant::fromValue(SensorData{23.5, 1013});
// 与QML桥接
Q_PROPERTY(QVariant config READ config) // QML侧读var
# 1.Z 元对象编译器(moc)原理
编译时而非运行时反射:
.h → moc扫描Q_OBJECT → moc_*.cpp:
staticMetaObject:
stringData[] ← 类名/属性名/方法名字符串
propertyData[] ← {name,type,notifySignalIdx,flags}
methodData[] ← {name,params,returnType}
qt_static_metacall(idx, args) ← 按索引dispatch到真实C++方法
与Java反射对比:编译期静态表→函数指针 vs 运行时HashMap→Method.invoke() 嵌入式ARM上前者快10-50倍。
# 1.5 QMetaType 与自定义类型注册
# 1.5.1 为什么需要注册类型
Qt 的元类型系统需要知道如何构造、复制、析构自定义类型——否则 QVariant、信号槽参数传递、QDataStream 序列化都无法工作:
struct SensorData {
float temperature;
int pressure;
QString unit;
};
// ❌ 默认情况下 SensorData 不能放入 QVariant
// ❌ 不能通过信号槽传递
// 注册步骤:
// ① 声明元类型
Q_DECLARE_METATYPE(SensorData)
// ② 【可选】注册比较器和流操作符
bool operator==(const SensorData& a, const SensorData& b) {
return a.temperature == b.temperature && a.pressure == b.pressure;
}
QDataStream& operator<<(QDataStream& out, const SensorData& d) {
return out << d.temperature << d.pressure << d.unit;
}
QDataStream& operator>>(QDataStream& in, SensorData& d) {
return in >> d.temperature >> d.pressure >> d.unit;
}
// ③ 注册到元类型系统
qRegisterMetaType<SensorData>("SensorData");
// 现在可以了!
QVariant v = QVariant::fromValue(SensorData{23.5f, 1013, "hPa"});
# 1.5.2 QMetaType 内部机制
// QMetaType 内部维护一个类型注册表
struct QMetaType::Registry {
// 类型 ID → 函数指针表
// { ctor, dtor, copy, compare, load, save, ... }
QHash<int, ConverterFunctions> converters;
};
// qRegisterMetaType 做的事:
// 1. 分配一个唯一的 typeId
// 2. 注册构造/析构/拷贝函数(模板推导)
// 3. 注册 QDataStream 操作符(如果已重载)
// 4. 注册比较操作符(如果已重载)
# 1.6 QObject 生命周期管理
# 1.6.1 deleteLater() 机制
deleteLater() 是 Qt 最精巧的内存管理之一:
obj->deleteLater();
// 不是立即 delete obj!
// 而是向当前线程的事件循环投递一个 DeferredDelete 事件
// 事件循环在下一个迭代中处理它 → 真正 delete obj
// 为什么这样做?
// 场景:在槽函数中删除自己
void MyWidget::onButtonClicked() {
delete this; // ❌ 之后访问成员变量 → 崩溃
}
void MyWidget::onButtonClicked() {
deleteLater(); // ✅ 当前函数安全执行完,下一轮才 delete
m_data->update(); // 这条语句仍可安全执行
}
deleteLater 注意事项:
- 如果对象没有线程事件循环 → 立即 delete(退化为普通 delete)
- 对象的父对象被删除时,子对象用普通 delete,不用 deleteLater
- 可以重复调用——只会投递一次 DeferredDelete 事件
# 1.6.2 对象树析构顺序
上一节已详述,这里补充禁止的操作:
// ❌ 禁止 1:栈上创建有父对象的 QObject
void foo() {
QObject parent;
QObject child(&parent); // child 的父对象是栈上对象
} // parent 先析构 → 尝试 delete child → child 被 delete 两次!
// ❌ 禁止 2:两个对象互为父子
parent->setParent(child);
child->setParent(parent); // Qt 会检测循环并警告
// ❌ 禁止 3:在构造函数中通过信号槽调用自己的槽
MyObject::MyObject() {
connect(this, &MyObject::ready, this, &MyObject::onReady);
emit ready(); // onReady 中访问未初始化的成员 → UB!
}
# 1.7 属性系统深入
# 1.7.1 属性绑定(Qt 6)
Qt 6 引入真正的属性绑定——类似于 QML 的行为:
class MyWidget : public QWidget {
Q_OBJECT
Q_PROPERTY(int width READ width WRITE setWidth BINDABLE bindableWidth)
Q_PROPERTY(int area READ area STORED false)
public:
MyWidget() {
// 属性绑定——width 变化时 area 自动更新
m_area.setBinding([this]() { return m_width * m_height; });
}
int width() const { return m_width; }
void setWidth(int w) { m_width = w; } // 旧式 setter
QBindable<int> bindableWidth() { return &m_width; }
int area() const { return m_area; }
private:
QProperty<int> m_width{100}; // Qt 6 可绑定属性
QProperty<int> m_height{100};
QProperty<int> m_area{10000}; // 绑定到 width * height
};
// 使用
widget.property("width").setValue(200);
// → m_width = 200
// → area 绑定自动计算 → m_area = 20000
QProperty vs Q_PROPERTY 对比:
| Q_PROPERTY(旧) | QProperty | |
|---|---|---|
| 绑定支持 | 仅 QML | C++ + QML |
| 变更通知 | 手动 emit signal | 自动(内部) |
| 类型安全 | QVariant 转换 | 模板类型 |
| 性能 | QVariant 装箱/拆箱 | 直接访问 |
| 与 QML 交互 | ✅ | ✅ |
# 1.7.2 QEnum 和 QFlag
class MyWidget : public QWidget {
Q_OBJECT
public:
enum Priority { Low, Normal, High, Critical };
Q_ENUM(Priority) // 注册到元对象 → QML 中可用 "MyWidget.Low"
enum Option {
OptionA = 0x01,
OptionB = 0x02,
OptionC = 0x04
};
Q_DECLARE_FLAGS(Options, Option)
Q_FLAG(Options) // 位或组合 → Qt 自动支持 "OptionA | OptionB"
};
Q_DECLARE_OPERATORS_FOR_FLAGS(MyWidget::Options)
# 1.8 综合案例
# 案例:完整的数据管理对象
class DataManager : public QObject {
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_PROPERTY(QString status READ status BINDABLE bindableStatus)
public:
DataManager() {
// 绑定 status 属性
m_status.setBinding([this]() {
if (m_data.size() > m_warningThreshold) return "Warning";
if (m_data.isEmpty()) return "Empty";
return QString("OK (%1)").arg(m_data.size());
});
}
int count() const { return m_data.size(); }
QString status() const { return m_status; }
QBindable<QString> bindableStatus() { return &m_status; }
Q_INVOKABLE void addData(const SensorData& data) {
m_data.append(data);
emit countChanged(m_data.size());
// m_status 自动更新(绑定)—无需手动设置!
}
Q_INVOKABLE SensorData getData(int index) const {
return m_data.at(index);
}
void setWarningThreshold(int t) {
m_warningThreshold = t;
m_status.markDirty(); // 标记脏,触发绑定重新计算
}
signals:
void countChanged(int count);
private:
QList<SensorData> m_data;
QProperty<int> m_count{0};
QProperty<QString> m_status{"Empty"};
int m_warningThreshold = 100;
};
# 1.9 速查表
| 宏/类 | 核心作用 |
|---|---|
Q_OBJECT | 启用元对象系统(信号、槽、属性) |
Q_GADGET | 为非 QObject 类添加元对象能力 |
Q_PROPERTY | 声明属性(READ/WRITE/NOTIFY/BINDABLE) |
Q_INVOKABLE | 标记方法可从 QML / 反射调用 |
Q_ENUM / Q_FLAG | 向 QML 暴露枚举 |
Q_DECLARE_METATYPE | 注册自定义类型 |
qRegisterMetaType<T>() | 运行时注册类型(信号槽跨线程必须) |
QMetaObject | 访问类的元信息(方法、属性) |
QMetaObject::invokeMethod | 动态调用方法(跨线程安全) |
QProperty<T> | C++ 可绑定属性(Qt 6) |
QBindable<T> | 获取属性的可绑定接口 |
QVariant | 通用类型容器 |
setProperty() / property() | 动态属性读写 |
deleteLater() | 延迟删除(事件循环下一轮) |
核心基础铁律:
- 继承 QObject 的类必须包含
Q_OBJECT宏——否则信号槽不工作 - 跨线程传递自定义类型必须调用
qRegisterMetaType<T>() - 不要在构造函数中
emit信号调用自己的槽——成员未初始化 - 栈上 QObject 不能有非栈上的子对象——析构顺序导致 double delete
deleteLater()要求线程有事件循环——否则立即 delete- Qt 6 优先使用
QProperty<T>代替旧式Q_PROPERTY——类型安全 + 自动绑定