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 内部事件(
QTimer、QMetaObject::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(),否则视图会崩溃。
代理深入
代理不仅可以绘制内容,还能创建编辑器。实现 QStyledItemDelegate 的 createEditor()、setEditorData()、setModelData() 可以完全控制编辑行为。
多线程:工作线程与主线程的桥梁
Qt 线程安全有一条铁律:GUI 操作只能在主线程执行。辅助线程必须通过信号槽与主线程通信。
工作线程的两种经典模式
- 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();
- 继承 QThread
重写run()方法,但这种方式容易出错且不便于管理信号槽,通常只在极简单或需要直接控制事件循环时使用。
并发工具
QtConcurrent::run:将函数扔进线程池,通过QFuture和QFutureWatcher监控结果。QMutex、QReadWriteLock、QSemaphore:标准同步原语。QMutexLocker:RAII 风格的锁,避免忘记解锁。
属性系统与动态属性
Q_PROPERTY 定义的属性不仅可以在对象间共享,还能通过样式表、动画框架访问。
使用场景
- 样式表:
qproperty-<propertyName>:可以从样式表中直接设置属性值。 - 动画:
QPropertyAnimation可以直接动画任何可读写的属性,只需该属性所属对象继承自 QObject。 - 动态属性:
setProperty("name", value)可以给对象添加运行时属性,无需在类中声明,适合临时标记或传递附加数据。
ui->widget->setProperty("urgent", true);
// 在样式表中使用:
// QWidget[urgent="true"] { background-color: red; }