Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

本页目录

元对象与信号槽通信契约 ​

1. 一条连接要回答六问 ​

  1. sender 和 receiver/context 谁拥有?
  2. 槽最终在哪个线程执行?
  3. 参数按什么语义复制/共享?
  4. receiver 销毁时连接是否自动断开?
  5. 高频信号积压时如何背压/合并?
  6. 关闭时队列中的旧事件是否仍有效?

信号槽解决解耦和调度,不自动解决所有权、线程安全与流量控制。

2. MOC 协议 ​

Q_OBJECT 让类拥有 metaObject、信号分派、属性、qobject_cast 等支持。生成代码布局是 Qt 实现细节,不应手工依赖 moc 内部索引。

函数指针 connect 提供编译期签名检查;字符串语法主要用于旧代码/动态场景,重构安全较弱。

3. Connection type ​

类型槽执行位置
Direct发射信号的当前线程,调用栈内同步执行
Queuedreceiver 线程事件循环稍后执行
Auto发射时按当前线程与 receiver affinity 决定 direct/queued
BlockingQueued排队并阻塞发射线程直到槽完成

Auto 不是简单比较 sender/receiver 对象 affinity,而是发射时当前执行线程与 receiver 所在线程。Direct 跨线程会在发射线程直接调用 receiver 代码,必须自行证明安全。

BlockingQueued 在同线程使用会死锁;跨线程也容易与反向锁/关闭形成死锁,只用于严格受控 RPC 式边界。

4. Context object ​

cpp
connect(reply, &QNetworkReply::finished,
        this, [this, reply] { handle(reply); });
1
2

给 Lambda 提供 this context,使 context 销毁后连接自动断开,并决定 queued 执行线程。无 context 的长期 Lambda 连接容易捕获悬空指针。

自动断开不撤销已经启动的外部工作;reply/worker 的所有权和取消仍需处理。

5. Queued 参数 ​

Queued 调用需把参数保存到事件中。自定义类型通常需要 Q_DECLARE_METATYPE,运行时场景可能还需 qRegisterMetaType;以当前 Qt 版本和连接语法为准。

传裸指针只复制地址,不保活对象。大型容器按值可能产生复制/共享分离成本;可使用不可变 shared data、ID + 数据仓库或批量快照,但明确线程安全。

6. 积压与合并 ​

进度每个迭代发信号可能淹没 GUI event queue。策略:

  • 限频到例如每帧/几十毫秒;
  • 只保存最新进度,单个 queued 更新读取最新值;
  • 批量发送结果;
  • receiver 落后时丢弃过期可替代事件;
  • 完成/错误事件不可被进度覆盖。

7. 连接身份与断开 ​

保存 QMetaObject::Connection 可在状态切换时显式断开。Qt::UniqueConnection 对某些 Lambda/函数对象形式有限制,不能把它当所有重复连接的万能检测。设计上让 connect 位置只执行一次更可靠。

8. 面试速答 ​

AutoConnection 何时决定类型? 发射信号时。

QueuedConnection 是否同步阻塞 sender? 不会,事件入队后返回;BlockingQueued 才阻塞。

receiver 销毁后连接怎样? QObject 连接会自动移除,但外部任务和捕获的其他裸指针不自动安全。

9. 自测答案 ​

GUI receiver 被 moveToThread 是否可行?QWidget 不可移动到工作线程;一般 QObject 也要求无 parent 才能 move。传 const BigData& 的 queued signal 是否零拷贝?签名写引用不代表排队事件不保存参数,需按元类型复制语义评估。

元对象代码从哪里来 ​

text
header with Q_OBJECT
 -> moc scans declarations
 -> generated meta-object C++
 -> compile/link with target
 -> runtime QMetaObject tables + qt_metacall
1
2
3
4
5

出现 undefined vtable/metaObject 常要检查 AUTOMOC、源文件是否加入 target、Q_OBJECT 所在头是否被扫描,而不是只补一个虚析构。

新式 connect 在编译期检查什么 ​

cpp
connect(sender, &Sender::valueChanged,
        receiver, &Receiver::updateValue);
1
2

编译器检查成员指针与参数兼容;运行时仍决定对象生命、连接类型和线程投递。lambda connect 应提供 context 对象,使 context 析构时自动断开并决定 queued 执行线程。

Connection 是一份路由契约 ​

text
sender signal
 -> connection record [receiver/context, method/thunk, type]
 -> Direct: current call stack
 -> Queued: copy args into event -> receiver event loop
1
2
3
4

AutoConnection 在 emit 时根据当前执行线程与 receiver affinity 决定,不是 connect 创建时永久决定。

queued 参数与元类型 ​

参数要能安全跨事件队列保存。自定义类型应具有明确复制/移动语义,并按 Qt 版本/API 注册元类型;不要传裸指针指向短命对象。大对象可用共享不可变值或 ID,但所有权要明确。

自动断开与排队事件 ​

sender/receiver/context 销毁会使连接失效,防止未来派发;已经入队的元调用如何处理依赖接收对象事件投递与销毁状态。关闭不能只调用 disconnect 就假定所有业务工作停止,还需取消在途任务和等待线程。

DirectConnection 的重入 ​

Direct slot 在 emit 调用栈立即执行,可能回调 sender、修改正在迭代的容器或触发嵌套事件:

text
emit -> slot -> changes model -> emits another signal -> nested slot
1

信号发射点要维持可重入不变量,或通过 queued connection 明确延后;后者又引入异步顺序和生命问题。

BlockingQueuedConnection 的死锁图 ​

text
T1 emits blocking to T2 and waits
T2 waits lock held by T1 / emits blocking back to T1
 -> deadlock
1
2
3

同线程使用 blocking queued 也会自锁。它只适合严格控制的跨线程同步边界,通常更好用异步结果/状态机。

属性与反射边界 ​

Q_PROPERTY 提供名字、读写方法、NOTIFY 等元数据,适合 QML、编辑器和序列化适配;它不自动保存字段或验证线程。属性 setter 仍需维护不变量,NOTIFY 应在值真正改变后发出并避免循环绑定。

连接诊断清单 ​

  1. sender/receiver/context 谁活得更久?
  2. emit 时在哪个线程,receiver affinity 在哪?
  3. queued 参数可否安全复制并注册?
  4. 是否可能重入或反馈环?
  5. 是否重复 connect,需要 UniqueConnection/令牌?
  6. 关闭时已排队/在途工作怎样处理?

连续追问与自测 ​

问:signal 是否一定异步? 答:不一定,Direct/同线程 Auto 常同步;跨线程 Auto 通常 queued。

问:context lambda connect 的价值? 答:自动断开与确定接收线程/生命周期边界。

  1. moc 是编译器吗? 答:它生成补充 C++ 元对象代码,之后仍由 C++ 编译器编译。
  2. AutoConnection 何时决定 direct/queued? 答:信号发射时根据线程关系。
  3. disconnect 能否取消业务线程任务? 答:不能,只影响信号路由,任务需独立取消协议。

工程深化:把本篇知识落到项目里 ​

这一节不是为了凑篇幅,而是把《元对象与信号槽通信契约》从“知道概念”推进到“能在项目里稳定使用”。 阅读时可以把每个知识点都追问成四件事:它保护什么边界,失败时有什么现象,怎样最小复现,怎样写进团队流程。

text
concept -> boundary -> failure signal -> minimal proof -> project rule
1

工程切片 1:最小可复现样例 ​

**场景。**围绕 moc 建一个很小的案例,不要一开始就放进完整业务系统。把问题压缩到一个可以提交给同事的目录,保留源码、构建命令、版本输出和预期现象。

**边界。**先判断这里讨论的是 moc、信号槽 还是 连接类型 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> moc -> 信号槽 -> 连接类型 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 2:接口边界 ​

**场景。**围绕 信号槽 建一个很小的案例,不要一开始就放进完整业务系统。写清调用方能依赖什么、不能依赖什么,并把隐式假设转换成命名函数、配置项或测试。

**边界。**先判断这里讨论的是 信号槽、连接类型 还是 元对象 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 信号槽 -> 连接类型 -> 元对象 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 3:失败注入 ​

**场景。**围绕 连接类型 建一个很小的案例,不要一开始就放进完整业务系统。故意制造一个常见错误,让日志、断言或测试先失败,再用修复后的证据说明规则生效。

**边界。**先判断这里讨论的是 连接类型、元对象 还是 线程亲和性 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 连接类型 -> 元对象 -> 线程亲和性 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 4:跨平台差异 ​

**场景。**围绕 元对象 建一个很小的案例,不要一开始就放进完整业务系统。至少比较 Windows、Linux 或不同编译器下的一个差异,记录差异属于标准、实现还是平台约定。

**边界。**先判断这里讨论的是 元对象、线程亲和性 还是 断开连接 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 元对象 -> 线程亲和性 -> 断开连接 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 5:性能观察 ​

**场景。**围绕 线程亲和性 建一个很小的案例,不要一开始就放进完整业务系统。用小规模和压力规模各跑一次,区分算法成本、同步成本、I/O 成本和工具链配置成本。

**边界。**先判断这里讨论的是 线程亲和性、断开连接 还是 moc 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 线程亲和性 -> 断开连接 -> moc -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 6:生命周期 ​

**场景。**围绕 断开连接 建一个很小的案例,不要一开始就放进完整业务系统。标出资源创建、转移、共享、停止和销毁的顺序,特别关注错误返回和提前退出路径。

**边界。**先判断这里讨论的是 断开连接、moc 还是 信号槽 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 断开连接 -> moc -> 信号槽 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 7:诊断证据 ​

**场景。**围绕 moc 建一个很小的案例,不要一开始就放进完整业务系统。保留命令、日志、符号、栈、测试输出或截图,让结论可以被另一个环境重新验证。

**边界。**先判断这里讨论的是 moc、信号槽 还是 连接类型 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> moc -> 信号槽 -> 连接类型 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 8:维护策略 ​

**场景。**围绕 信号槽 建一个很小的案例,不要一开始就放进完整业务系统。把一次性经验沉淀到 README、CI、脚本、示例工程或检查表里,避免只存在个人记忆中。

**边界。**先判断这里讨论的是 信号槽、连接类型 还是 元对象 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 信号槽 -> 连接类型 -> 元对象 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 9:版本演进 ​

**场景。**围绕 连接类型 建一个很小的案例,不要一开始就放进完整业务系统。为未来变更预留兼容策略:版本号、特性位、弃用窗口、迁移脚本和回滚入口。

**边界。**先判断这里讨论的是 连接类型、元对象 还是 线程亲和性 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 连接类型 -> 元对象 -> 线程亲和性 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 10:最小消费者 ​

**场景。**围绕 元对象 建一个很小的案例,不要一开始就放进完整业务系统。从使用者角度写一个小程序或小工程,只依赖公开接口,验证安装包、头文件和运行时行为。

**边界。**先判断这里讨论的是 元对象、线程亲和性 还是 断开连接 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 元对象 -> 线程亲和性 -> 断开连接 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 11:边界复盘 ​

**场景。**围绕 线程亲和性 建一个很小的案例,不要一开始就放进完整业务系统。用一张表说明问题发生在源码、编译、链接、加载、运行、关闭或发布中的哪一层。

**边界。**先判断这里讨论的是 线程亲和性、断开连接 还是 moc 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 线程亲和性 -> 断开连接 -> moc -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 12:反例训练 ​

**场景。**围绕 断开连接 建一个很小的案例,不要一开始就放进完整业务系统。写出一个看似能工作但不可维护的方案,再说明它在规模、团队协作或发布时会怎样失败。

**边界。**先判断这里讨论的是 断开连接、moc 还是 信号槽 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 断开连接 -> moc -> 信号槽 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 13:最小可复现样例 ​

**场景。**围绕 moc 建一个很小的案例,不要一开始就放进完整业务系统。把问题压缩到一个可以提交给同事的目录,保留源码、构建命令、版本输出和预期现象。

**边界。**先判断这里讨论的是 moc、信号槽 还是 连接类型 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> moc -> 信号槽 -> 连接类型 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 14:接口边界 ​

**场景。**围绕 信号槽 建一个很小的案例,不要一开始就放进完整业务系统。写清调用方能依赖什么、不能依赖什么,并把隐式假设转换成命名函数、配置项或测试。

**边界。**先判断这里讨论的是 信号槽、连接类型 还是 元对象 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 信号槽 -> 连接类型 -> 元对象 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 15:失败注入 ​

**场景。**围绕 连接类型 建一个很小的案例,不要一开始就放进完整业务系统。故意制造一个常见错误,让日志、断言或测试先失败,再用修复后的证据说明规则生效。

**边界。**先判断这里讨论的是 连接类型、元对象 还是 线程亲和性 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 连接类型 -> 元对象 -> 线程亲和性 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 16:跨平台差异 ​

**场景。**围绕 元对象 建一个很小的案例,不要一开始就放进完整业务系统。至少比较 Windows、Linux 或不同编译器下的一个差异,记录差异属于标准、实现还是平台约定。

**边界。**先判断这里讨论的是 元对象、线程亲和性 还是 断开连接 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 元对象 -> 线程亲和性 -> 断开连接 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 17:性能观察 ​

**场景。**围绕 线程亲和性 建一个很小的案例,不要一开始就放进完整业务系统。用小规模和压力规模各跑一次,区分算法成本、同步成本、I/O 成本和工具链配置成本。

**边界。**先判断这里讨论的是 线程亲和性、断开连接 还是 moc 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 线程亲和性 -> 断开连接 -> moc -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 18:生命周期 ​

**场景。**围绕 断开连接 建一个很小的案例,不要一开始就放进完整业务系统。标出资源创建、转移、共享、停止和销毁的顺序,特别关注错误返回和提前退出路径。

**边界。**先判断这里讨论的是 断开连接、moc 还是 信号槽 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 断开连接 -> moc -> 信号槽 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 19:诊断证据 ​

**场景。**围绕 moc 建一个很小的案例,不要一开始就放进完整业务系统。保留命令、日志、符号、栈、测试输出或截图,让结论可以被另一个环境重新验证。

**边界。**先判断这里讨论的是 moc、信号槽 还是 连接类型 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> moc -> 信号槽 -> 连接类型 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 20:维护策略 ​

**场景。**围绕 信号槽 建一个很小的案例,不要一开始就放进完整业务系统。把一次性经验沉淀到 README、CI、脚本、示例工程或检查表里,避免只存在个人记忆中。

**边界。**先判断这里讨论的是 信号槽、连接类型 还是 元对象 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 信号槽 -> 连接类型 -> 元对象 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 21:版本演进 ​

**场景。**围绕 连接类型 建一个很小的案例,不要一开始就放进完整业务系统。为未来变更预留兼容策略:版本号、特性位、弃用窗口、迁移脚本和回滚入口。

**边界。**先判断这里讨论的是 连接类型、元对象 还是 线程亲和性 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 连接类型 -> 元对象 -> 线程亲和性 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 22:最小消费者 ​

**场景。**围绕 元对象 建一个很小的案例,不要一开始就放进完整业务系统。从使用者角度写一个小程序或小工程,只依赖公开接口,验证安装包、头文件和运行时行为。

**边界。**先判断这里讨论的是 元对象、线程亲和性 还是 断开连接 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 元对象 -> 线程亲和性 -> 断开连接 -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

工程切片 23:边界复盘 ​

**场景。**围绕 线程亲和性 建一个很小的案例,不要一开始就放进完整业务系统。用一张表说明问题发生在源码、编译、链接、加载、运行、关闭或发布中的哪一层。

**边界。**先判断这里讨论的是 线程亲和性、断开连接 还是 moc 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 线程亲和性 -> 断开连接 -> moc -> observable result
1

**验证。**记录一条能重复运行的命令或测试,并写明成功输出和失败输出。只说“本机能跑”不够,至少要记录工具版本、构建类型和关键参数。

**常见错误。**把偶然通过当成规则、把私有实现当成公开契约、或者让错误路径绕开清理逻辑,都会让本篇主题在真实项目里变得不可维护。

**复盘问题。**如果明天换一个编译器、Qt 版本、插件版本、输入规模或发布目录,这个结论是否仍成立?不成立时,应该由文档、测试还是 CI 给出提示?

本篇收束 ​

掌握《元对象与信号槽通信契约》的标志,不是记住所有名词,而是能把 moc、信号槽、连接类型、元对象、线程亲和性、断开连接 放进一条可验证的工程链路。 先用最小样例建立判断,再用测试和脚本固定判断,最后把失败证据留给未来的自己和团队。

最后更新于:

Pager
下一篇← C++ 编程 / C++ Programming

持续记录,持续成长

Copyright © Tidenflow