☰
QObject使用详解
2026/10/27 0:27:23 网站建设 项目流程

一、QObject核心定位

QObject是Qt所有可支持信号槽、内存自动管理、事件循环、元对象系统类的基类,位于<QObject>头文件。几乎所有Qt控件、网络、线程、定时器等组件都直接/间接继承自它。

核心特性(全部依赖元对象系统MOC):

  1. 信号与槽(Signal & Slot):Qt跨对象通信机制

  2. 父子内存管理:父对象销毁自动回收所有子对象

  3. 元对象反射:运行时获取类名、属性、方法、枚举

  4. 事件系统:接收、分发、过滤事件

  5. 对象名称+动态属性:objectName、setProperty

  6. 跨线程信号槽:支持多线程安全通信


二、使用前置必备:moc元对象宏

1. Q_OBJECT宏

只要类需要信号槽、属性、元对象反射,类定义内第一行必须写Q_OBJECT,否则moc不会生成元代码,信号槽直接失效。

#include <QObject> class MyObj : public QObject{ Q_OBJECT // 关键宏,放在类内部开头public: explicit MyObj(QObject *parent = nullptr);};

注意事项:

(1) Q_OBJECT只能在继承QObject的类中使用

(2) 类必须是public继承QObject

(3) qmake/CMake会自动调用moc扫描带Q_OBJECT的头文件生成.moc源码

(4) 忘记加会报:undefined reference to vtable链接错误

2. 其他配套元对象宏

宏

作用

Q_PROPERTY

定义可反射属性,支持QML /脚本读写

Q_SIGNALS

标记信号函数段(可省略,仅可读性)

Q_SLOTS

标记槽函数(可省略)

Q_ENUM

导出枚举到元对象,运行时转字符串

Q_INVOKABLE

普通成员函数可通过metaObject()->invokeMethod()动态调用


三、父子对象与内存自动管理(最常用特性)

1. 构造函数 parent 参数

所有 QObject 子类标准构造:

explicit MyObj(QObject *parent = nullptr);
  • parent为父QObject对象指针

  • 创建时传入父对象,自动加入父对象子对象列表

2. 销毁规则(核心)

父对象析构时,会自动delete所有子对象,递归销毁,无需手动释放内存,杜绝内存泄漏。

示例:

// 栈父对象,出作用域自动销毁子对象 void test(){ QObject parentObj; // child1、child2 归属 parentObj QObject *child1 = new QObject(&parentObj); QObject *child2 = new QObject(child1); // 函数结束 parentObj 析构 → 先删 child1 → 再删 child2 }

3. 常用父子操作API

// 设置父对象 void setParent(QObject *parent); // 获取父对象 QObject *parent() const; // 获取所有一级子对象列表 QList<QObject*> children() const; // 根据 objectName 查找子对象(递归查找) template<typename T> T findChild(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindDirectChildrenOnly); QList<T> findChildren(...)

findChild实用场景

界面中根据名称查找控件、动态管理子业务对象:

QPushButton *btn = parentWidget->findChild<QPushButton>("btn_submit");

4. 手动释放注意

  1. 不要栈对象当父,子对象堆创建后手动delete父,会双重释放崩溃

  2. 多线程:父子不能跨线程,父和子必须在同一线程,跨线程会警告+内存异常

  3. 窗口控件(QWidget)自带父子管理,布局控件会自动设置 parent


四、信号与槽Signal&Slot(QObject核心通信)

1. 信号signals

  • 关键字 signals: 下声明,无实现、无返回值、权限默认 protected

  • 触发:emit 信号名(参数);

class MyObj : public QObject{ Q_OBJECT signals: void sendMsg(const QString &text); // 信号,只声明不写函数体public: void testEmit() { emit sendMsg("hello qobject"); // 发射信号 }};

2. 槽slots

三种合法槽函数:

(1) public slots: / private slots: 显式标记

(2) 普通 public 成员函数(Qt5 新 connect 语法支持)

(3) 标记 Q_INVOKABLE 的函数

class Receiver : public QObject{ Q_OBJECTpublic slots: void onRecv(const QString &msg) { qDebug() << "收到:" << msg; }};

3. connect四种写法(Qt4老式/Qt5新式推荐)

方式 1:Qt5函数指针(推荐,编译期类型检查)

MyObj sender; Receiver recv;// connect(发送者, 信号地址, 接收者, 槽地址) connect(&sender, &MyObj::sendMsg, &recv, &Receiver::onRecv); sender.testEmit(); // 触发打印

方式 2:Lambda表达式(无接收对象,临时逻辑)

connect(&sender, &MyObj::sendMsg, this, [](QString s){ qDebug() << "lambda接收:" << s;});

方式 3:Qt4字符串宏(运行时检查,易写错不推荐)

connect(&sender, SIGNAL(sendMsg(QString)), &recv, SLOT(onRecv(QString)));

方式 4:QMetaObject::connectSlotsByName 自动绑定

规则:槽函数名 on_对象名_信号名,UI控件常用

// 控件 objectName="btn_ok",clicked 信号 void on_btn_ok_clicked();// 调用一次自动绑定所有匹配槽 QMetaObject::connectSlotsByName(this);

4. connect 第五参数:连接类型(跨线程必用)

connect(sender, signal, receiver, slot, Qt::ConnectionType);

枚举

适用场景

Qt::AutoConnection默认

同线程=直连;跨线程=队列

Qt::DirectConnection直连

信号发射线程直接执行槽,跨线程慎用(线程不安全)

Qt::QueuedConnection队列

信号放入接收者线程事件循环,跨线程标准用法

Qt::BlockingQueuedConnection阻塞队列

发射线程阻塞直到槽执行完成,禁止同线程死锁

Qt::UniqueConnection

防止重复连接,搭配或使用`

5. 断开连接 disconnect

// 断开指定信号槽 disconnect(&sender, &MyObj::sendMsg, &recv, &Receiver::onRecv); // 断开sender所有连接 disconnect(&sender, nullptr, nullptr, nullptr); // 断开接收者所有连接 sender.disconnect(&recv);

五、元对象系统MetaObject(反射能力)

1. 获取元对象

QObject *obj = new MyObj; const QMetaObject *meta = obj->metaObject(); // 静态获取,无需实例 const QMetaObject *metaStatic = &MyObj::staticMetaObject;

2. 运行时反射常用API

// 获取类名 qDebug() << meta->className(); // 动态调用函数(函数需 Q_INVOKABLE / slot) QString res; meta->invokeMethod(obj, "getInfo", Qt::DirectConnection, Q_RETURN_ARG(QString, res)); // 获取属性数量、读写属性 int propCnt = meta->propertyCount(); QMetaProperty prop = meta->property(0); prop.read(obj); prop.write(obj, "newVal");

3. Q_PROPERTY动态属性示例

class Person : public QObject{ Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)public: QString name() const { return m_name; } void setName(const QString &n) { if(m_name != n) { m_name = n; emit nameChanged(m_name); } } signals: void nameChanged(const QString &);private: QString m_name;};

使用:

Person p; p.setProperty("name", "张三"); // 动态写 qDebug() << p.property("name").toString(); // 动态读

4. 动态属性(无Q_PROPERTY也能用)

无需提前声明,任意QObject运行时附加键值:

obj->setProperty("tag", 100); int tag = obj->property("tag").toInt(); // 判断是否存在属性 obj->dynamicPropertyNames();

六、事件处理机制(QObject事件循环)

所有 QObject 都能接收事件,依靠 event()、事件过滤器。

1. 重写事件处理函数

class MyObj : public QObject{ Q_OBJECTprotected: // 总事件分发入口 bool event(QEvent *event) override { if(event->type() == QEvent::Timer) { qDebug() << "定时器事件"; return true; // 消费事件,不再传递 } return QObject::event(event); // 交给基类默认处理 }};

2. 事件过滤器 installEventFilter

A 对象监听 B 对象所有事件,拦截处理:

// 监听者this安装过滤器到 targetObj targetObj->installEventFilter(this); // 重写过滤函数 bool eventFilter(QObject *watched, QEvent *event) override{ if(watched == targetObj && event->type() == QEvent::MouseButtonPress) { return true; // 拦截鼠标点击,目标收不到 } return false; // 放行事件 }

3. 发送事件

同步:QCoreApplication::sendEvent(obj, &event) 阻塞执行

异步:QCoreApplication::postEvent(obj, new QEvent(...)) 放入事件队列


七、QObject多线程规则

1. 对象归属线程

每个 QObject 绑定创建时所在线程,API:

// 返回对象依附的线程 QThread *thread() const; // 移动对象到新线程(必须无父对象!) bool moveToThread(QThread *targetThread);

2. 强制铁则

(1)moveToThread 前必须 setParent (nullptr),有父对象移动失败

(2)不能跨线程父子对象,父和子必须同线程

(3)不要在子线程直接操作 UI 控件(UI 只能主线程)

(4)跨线程通信信号槽必须用 Qt::QueuedConnection

标准多线程模板

QThread *th = new QThread; Worker *work = new Worker; work->moveToThread(th); // 移至子线程 // 线程启动执行任务 connect(th, &QThread::started, work, &Worker::doWork); // 任务完成销毁线程 connect(work, &Worker::finished, th, &QThread::quit);connect(th, &QThread::finished, th, &QThread::deleteLater);connect(th, &QThread::finished, work, &Worker::deleteLater); th->start();

3. deleteLater 延迟删除

多线程安全销毁,不会立即释放,等待当前事件循环完成再删除:

obj->deleteLater();

推荐替代直接 delete obj,避免事件派发中崩溃。


八、对象名称objectName

// 设置/获取对象名void setObjectName(const QString &name); QString objectName() const;

用途:

  1. findChild 按名称查找对象

  2. QSS 样式选择器 #objectName

  3. 日志、调试区分多个同类对象


九、禁用拷贝(QObject无法复制)

QObject 禁用拷贝构造、赋值运算符:

Q_DISABLE_COPY(MyObj) // 内部自动加,无需手动写

错误写法:

MyObj a; MyObj b = a; // 编译报错,禁止拷贝

原因:父子内存管理、信号槽绑定、线程归属无法复制,需要多实例只能 new 创建。


十、常用全局配套工具类

  1. QCoreApplication:管理事件循环,所有QObjec 事件分发入口

  2. QMetaType:自定义类型注册,信号槽传递自定义结构体需qRegisterMetaType<T>()

  3. QVariant:配合 Q_PROPERTY 存储任意类型属性


十一、常见报错与踩坑总结

  1. undefined reference to vtable:类有Q_OBJECT但未moc,或忘记执行 qmake/CMake

  2. 跨线程崩溃:父子跨线程、DirectConnection 跨线程直接调用 UI、未用 QueuedConnection

  3. 重复析构崩溃:栈对象作为父,子对象手动 delete;或 moveToThread 时有父对象

  4. 信号槽不触发:参数类型不匹配、connect 第五参数连接类型错误、对象提前销毁

  5. invokeMethod 调用失败:函数未加 slot / Q_INVOKABLE,参数传参不匹配


十二、最简完整可运行示例

#include <QCoreApplication>#include <QObject>#include <QDebug> class Sender : public QObject{ Q_OBJECT signals: void msg(const QString &s); public: void send() { emit msg("测试信号"); } }; class Recv : public QObject { Q_OBJECT public slots: void onMsg(QString s) { qDebug() << "接收:" << s; } }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); Sender s; Recv r; QObject::connect(&s, &Sender::msg, &r, &Recv::onMsg); s.send(); return a.exec(); } #include "main.moc" // 单文件 moc 必需

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询