Qt 开发终极坑点手册图表版本
目录
🚨 Qt 开发终极坑点手册(2025 实战版)
🧵 一、线程与并发(Thread / Concurrency)
🔗 二、信号 / 槽与跨线程通信(Signal / Slot)
🗃️ 三、数据库(Database / Persistence)
🌐 四、网络 / 串口 / Modbus / I/O
📊 五、模型 / 视图 / QML 数据同步
♻️ 六、内存与对象生命周期(Memory / Object Lifetime)
⏱️ 七、性能与多线程设计(Performance)
⚙️ 八、构建 / 跨平台 / 环境
🧰 九、日志与调试(Debug / Logging)
🧩 十、口诀速记
🚨 Qt 开发终极坑点手册(2025 实战版)
🧵 一、线程与并发(Thread / Concurrency)
| ⚠️ 问题 | 📘 原因 | ✅ 正确做法 |
|---|---|---|
| QSqlDatabase 跨线程使用崩溃 | 每个连接绑定创建线程,内部非线程安全 | 每线程独立 addDatabase();线程退出前 close() + removeDatabase() |
| QObject 跨线程直接操作 | 对象归属线程固定,方法非线程安全 | 用信号槽或 QMetaObject::invokeMethod(Qt::QueuedConnection) |
| QTimer 不触发 | 所在线程无事件循环 | 线程内需 exec() 或放在主线程 |
| BlockingQueuedConnection 死锁 | 双向阻塞线程等待 | 少用;优先 QueuedConnection |
| 线程退出后对象还活着 | 没调用 quit() + wait() | 结束前调用 thread->quit(); thread->wait(); |
🔗 二、信号 / 槽与跨线程通信(Signal / Slot)
| ⚠️ 问题 | 📘 原因 | ✅ 正确做法 |
|---|---|---|
| 跨线程连接崩溃 | 未使用队列连接 | 跨线程信号自动是 QueuedConnection;也可显式指定 |
| 信号参数类型无法传递 | 未注册自定义类型 | 使用 Q_DECLARE_METATYPE + qRegisterMetaType<T>() |
| lambda 捕获悬空指针 | 捕获了临时对象或 this 已销毁 | 捕获智能指针或 QPointer;避免捕获裸引用 |
| 对象析构后信号仍触发 | 信号异步队列未清理 | 使用 QObject::deleteLater() 销毁,或手动断开连接 |
🗃️ 三、数据库(Database / Persistence)
| ⚠️ 问题 | 📘 原因 | ✅ 正确做法 |
|---|---|---|
| 跨线程写库失败 | Qt DB 连接线程绑定 | 每线程独立连接 |
| 数据库被锁 (SQLite) | 并发写入 | 开启 WAL 模式 PRAGMA journal_mode=WAL;,短事务+重试机制 |
| 长 SQL 卡 UI | 同步执行在主线程 | 放后台线程执行;结果信号发回主线程 |
| removeDatabase 崩溃 | 连接仍被 QSqlDatabase 拷贝持有 | 确保所有拷贝析构后再 remove |
🌐 四、网络 / 串口 / Modbus / I/O
| ⚠️ 问题 | 📘 原因 | ✅ 正确做法 |
|---|---|---|
| QSerialPort/QNetworkAccessManager 跨线程调用崩溃 | 这些对象必须运行在自己的线程事件循环 | 在线程内创建并操作;通过信号投递请求 |
| Modbus 数据错位 | 地址/字节序不匹配 | 手册 40001 是 1-based;代码用 0-based;注意高低字顺序 |
| 端口 502 无法监听 | 系统保留端口 | 改用 1502 或以管理员权限运行 |
| 跨机通信失败 | 防火墙未放行 | 确认 502/1502 端口开放 |
📊 五、模型 / 视图 / QML 数据同步
| ⚠️ 问题 | 📘 原因 | ✅ 正确做法 |
|---|---|---|
| 模型更新异常/崩溃 | begin/end 成对调用错误 | 用 beginInsertRows / endInsertRows 等包裹修改 |
| QML 不刷新 | 未发信号或 model 未 reset | 改变结构用 beginResetModel/endResetModel |
| 高频数据更新卡顿 | 每次都触发 QML 绑定 | 聚合或节流,50~200ms 更新一次 |
| 跨线程改模型 | 模型属于主线程 | 数据线程只发信号,不直接改模型 |
♻️ 六、内存与对象生命周期(Memory / Object Lifetime)
| ⚠️ 问题 | 📘 原因 | ✅ 正确做法 |
|---|---|---|
| 有父对象的子对象 moveToThread 崩溃 | QObject 父子关系不能跨线程 | 先 setParent(nullptr) 再移动 |
| 异步回调访问已释放对象 | 捕获了临时对象或生命周期不符 | 用 QPointer 检查对象是否有效 |
| QObject + 智能指针重复释放 | QObject 有自己销毁机制 | 不要用 std::shared_ptr<QObject>,改用 QPointer |
| QByteArray constData() 异步使用 | 内存可能释放 | 异步传递前复制数据 |
⏱️ 七、性能与多线程设计(Performance)
| ⚠️ 问题 | 📘 原因 | ✅ 正确做法 |
|---|---|---|
| 主线程卡顿 | 阻塞操作(SQL/网络/磁盘) | 一律放后台线程 |
| 频繁发信号耗性能 | 大量小包信号开销大 | 批量发送或缓存聚合 |
| 隐式深拷贝拖慢性能 | 容器写时拷贝触发复制 | 调整容器结构,提前 reserve() |
| 线程过多反而慢 | 创建销毁开销 | 控制线程数量,用线程池 / QtConcurrent |
⚙️ 八、构建 / 跨平台 / 环境
| ⚠️ 问题 | 📘 原因 | ✅ 正确做法 |
|---|---|---|
| 运行缺少平台插件 | 未带 plugins/platforms/ | 部署时带上 platforms, sqldrivers, imageformats |
| Release 崩溃但 Debug 正常 | 未初始化变量或数据竞争 | 开启 AddressSanitizer / ThreadSanitizer |
| 路径跨平台失效 | 斜杠硬编码 | 用 QDir, QStandardPaths |
| 字符乱码 | 本地编码差异 | 一律 UTF-8 (QString::fromUtf8) |
| 时间错乱 | 时区差异 | 内部统一存 UTC,显示时本地化 |
🧰 九、日志与调试(Debug / Logging)
| ⚠️ 问题 | 📘 原因 | ✅ 正确做法 |
|---|---|---|
| 日志混乱看不出线程 | 没打印线程信息 | 用 qInstallMessageHandler 自定义输出线程ID、时间 |
| 隐藏异常 | 没加断言 | 开发期使用 Q_ASSERT 或自定义检查 |
| 信号未触发难排查 | 无日志跟踪 | 在关键信号槽添加 qDebug() 或 category 日志 |
🧩 十、口诀速记
🧵 线程各有 DB;
💬 跨线程用信号;
🎨 GUI 只在主线程;
🕒 QTimer 要事件循环;
🧱 模型操作成对;
🧠 QObject 不乱跨线程;
🧾 UTF-8 + QDir 保跨平台;
⚡ 高频更新要节流;
🔍 日志加线程ID。
