Qt 深入解析

FreeGuideOnline 最新 2026-07-15

cpp class MyObject : public QObject { Q_OBJECT Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged) public: int value() const; void setValue(int v); signals: void valueChanged(int newValue); private: int m_value; };


### 信号与槽的真相
- **连接类型**:`Qt::AutoConnection` 会根据发送者和接收者是否在同一线程自动选择 `DirectConnection`(直连,等同于回调)或 `QueuedConnection`(跨线程,事件投递)。
- **性能开销**:直连调用的开销大约是普通虚函数调用的 4~10 倍,主要用在信号槽参数合并、线程安全检查。但这在 99% 的应用场景中完全可以接受。
- **新式语法**:推荐使用函数指针式的 connect,编译期即可检查类型错误。

```cpp
connect(sender, &Sender::signalName, receiver, &Receiver::slotName);

事件系统:所有动作的来源

Qt 应用通过 事件循环 驱动。QApplication::exec() 进入主事件循环,不断从平台获取事件,分发到对应对象。

事件循环与事件队列

  • 主线程自动拥有一个事件循环,额外的线程可以使用 QEventLoop 手动启动。
  • 事件分为:系统事件(鼠标、键盘)、Qt 内部事件QTimerQMetaObject::invokeMethod 的跨线程调用)。
  • 事件过滤器:可以在不继承目标类的情况下拦截事件,这是 AOP 思想的体现。
// 安装事件过滤器
watched->installEventFilter(this);
bool MyWatcher::eventFilter(QObject *obj, QEvent *event) {
    if (event->type() == QEvent::KeyPress) {
        // 处理按键
        return true; // 过滤掉
    }
    return QObject::eventFilter(obj, event);
}

事件传播 vs 信号槽

信号槽用于“有意的”组件间通信,是高层机制;事件用于底层输入和系统通知。许多 Qt 控件内部先将事件转换成信号(如 QPushButton 将鼠标点击转换成 clicked() 信号),再通知外部。


内存管理:父子关系与所有权

Qt 采用 对象树 模型管理 QObject 的生命周期。父对象删除时会自动删除所有子对象,这极大地减少了内存泄漏。

关键规则与陷阱

  • 必须在堆上创建 才能享受自动销毁机制。栈上创建的 QObject 如果被指定父对象,当父对象先析构时会导致 double-free。
  • 删除顺序:子对象最先销毁,父对象最后销毁。确保在析构函数中不需要访问已经销毁的子对象。
  • deleteLater():在对象所属线程的事件循环结束时才实际删除,适合需要延迟析构的场景(如对象内部正在处理事件时不能立即删除)。

模型 / 视图 / 代理:数据与显示分离

Qt 的 Model/View 架构将数据(Model)、呈现方式(View)、编辑交互(Delegate)彻底解耦。

核心抽象

  • QAbstractItemModel:你必须实现的纯虚基类,为视图提供数据。数据通过索引 QModelIndex 访问。
  • 标准模型QStandardItemModel(树形/表格数据)、QFileSystemModel(文件系统)、QSqlTableModel 等。
  • 性能关键:避免一次性加载全部数据,而应实现 canFetchMore()fetchMore() 进行分页加载。自定义模型时要正确处理 beginInsertRows()endInsertRows(),否则视图会崩溃。

代理深入

代理不仅可以绘制内容,还能创建编辑器。实现 QStyledItemDelegatecreateEditor()setEditorData()setModelData() 可以完全控制编辑行为。


多线程:工作线程与主线程的桥梁

Qt 线程安全有一条铁律:GUI 操作只能在主线程执行。辅助线程必须通过信号槽与主线程通信。

工作线程的两种经典模式

  1. moveToThread
    将 QObject 子类移动到新线程,之后通过信号触发对象槽函数,槽函数将在新线程执行。这是推荐方式。
QThread *thread = new QThread;
Worker *worker = new Worker;
worker->moveToThread(thread);
connect(thread, &QThread::started, worker, &Worker::doWork);
connect(worker, &Worker::finished, thread, &QThread::quit);
connect(thread, &QThread::finished, worker, &QObject::deleteLater);
thread->start();
  1. 继承 QThread
    重写 run() 方法,但这种方式容易出错且不便于管理信号槽,通常只在极简单或需要直接控制事件循环时使用。

并发工具

  • QtConcurrent::run:将函数扔进线程池,通过 QFutureQFutureWatcher 监控结果。
  • QMutexQReadWriteLockQSemaphore:标准同步原语。
  • QMutexLocker:RAII 风格的锁,避免忘记解锁。

属性系统与动态属性

Q_PROPERTY 定义的属性不仅可以在对象间共享,还能通过样式表、动画框架访问。

使用场景

  • 样式表qproperty-<propertyName>: 可以从样式表中直接设置属性值。
  • 动画QPropertyAnimation 可以直接动画任何可读写的属性,只需该属性所属对象继承自 QObject。
  • 动态属性setProperty("name", value) 可以给对象添加运行时属性,无需在类中声明,适合临时标记或传递附加数据。
ui->widget->setProperty("urgent", true);
// 在样式表中使用:
// QWidget[urgent="true"] { background-color: red; }