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

外观

本页目录

QObject 生命周期与线程亲和性 ​

1. Parent 是所有权,不是分类关系 ​

parent 析构时删除 children;child 提前析构会从 parent 列表移除。它不追踪任意引用、不处理无 parent 对象和一般引用环,因此不是 GC。

栈上 child 若 parent 比它晚析构会有风险,通常让栈 QObject 无 parent,或保证构造/析构顺序正确。不要让 parent 树和独立 smart pointer 同时认为自己负责 delete。

2. QPointer ​

QPointer<T> 是 guarded pointer:QObject 销毁时自动变 null。它不拥有对象,不让对象存活,也不使“检查后使用”跨线程原子安全。

cpp
QPointer<Panel> panel = ...;
if (panel) panel->refresh();
1
2

这适合 GUI 单线程事件间观察;跨线程仍应将调用排队到对象线程,并通过上下文/状态机管理生命周期。

3. deleteLater ​

deleteLater() 发布 deferred delete 事件,实际删除依赖对象线程的事件循环和 Qt 对线程结束的处理语义。它不是“任何时候跨线程 delete 都自动安全”的口令。

text
request deleteLater
  -> event queued to affinity thread
  -> current nested/event handling unwinds
  -> event loop processes deferred delete
  -> QObject destructor
1
2
3
4
5

事件循环已停止或对象线程关闭顺序错误时,清理可能延迟/遗漏。worker-object 常连接 QThread::finished 到 worker deleteLater,但仍需核对 quit、事件投递和外部所有者顺序。

4. 线程亲和性 ​

QObject 创建时属于当前线程。moveToThread 改变对象及其 children 的 affinity,要求对象无 parent。QThread 对象本身通常活在创建它的线程,不等于 run() 执行线程。

queued event/slot 在 affinity 线程事件循环处理。若该线程没有运行事件循环,queued 调用不会按预期执行。

5. Worker-object 关闭 ​

text
owner requests cancel
  -> queued stop / atomic cancellation state
  -> worker exits operation, emits finished
  -> thread.quit
  -> worker deferred delete
  -> thread finishes
  -> owner wait / deletes thread object
1
2
3
4
5
6
7

阻塞库调用必须有独立取消/超时,单纯 queued stop() 可能因 worker 正忙且事件循环不处理而永远不到达。

6. 非 QObject 资源 ​

parent tree 只删除 QObject。文件、GPU buffer、VTK pipeline、线程句柄和标准容器仍应用 RAII/明确 owner。QObject 析构在哪个线程发生会影响这些成员的析构要求。

7. 面试速答 ​

QObject parent 与 shared_ptr 可混用吗? 可以在非常明确的非重复所有权设计中出现,但不要让二者独立 delete 同一对象;通常择一所有者模型。

QPointer 是否线程安全弱引用? 不是通用同步方案,它是 QObject 销毁感知观察指针。

moveToThread 后成员函数都会自动在新线程执行吗? 不会,普通直接调用仍在调用线程;queued signal/event 才按 affinity 调度。

8. 自测答案 ​

对象有 parent 能否 moveToThread?不能直接移动,需先重新设计/移除 parent。deleteLater 后能否继续解引用?不能把对象视作稳定;调用已请求销毁,应停止发布新工作并用 QPointer/context 防旧回调。

QObject 树的析构顺序 ​

text
Window(parent)
├── Toolbar(child)
└── Panel(child)
    └── Button(grandchild)

delete Window -> children recursively deleted
1
2
3
4
5
6

子对象也可早于 parent 手工删除,QObject 会从父 children 列表移除。不要同时让独立 unique_ptr 与 parent 都认为自己负责删除同一对象,除非自定义 deleter/释放协议明确避免双删。

栈对象 parent 顺序陷阱 ​

若先构造 child 栈对象、后构造 parent,再把 child 设 parent,离开作用域时 parent 先析构并 delete 栈 child,随后 child 还会自动析构,造成错误。栈 QObject 应依靠作用域逆序,不把较早构造的栈对象交给较晚构造 parent 删除。

deleteLater 的事件语义 ​

text
call deleteLater
 -> post DeferredDelete event to object's thread
 -> control returns
 -> event loop reaches safe point
 -> QObject destructor runs
1
2
3
4
5

调用后对象并非立刻死亡,后续直接访问仍可能逻辑错误。若目标线程事件循环已停止,DeferredDelete 可能无法按预期处理;关闭流程要在 quit 前安排销毁或显式控制。

QPointer 与 weak_ptr 的差异 ​

QPointer 是 QObject 的非拥有受保护指针,对象析构时自动变 null;它不保活对象,也不能阻止“检查非空后另一线程删除”的竞态。QObject 通常要求亲和线程操作,跨线程应通过 queued 消息而不是拿 QPointer 当并发共享指针。

Thread affinity 决定事件在哪里处理 ​

QObject 默认属于创建线程,thread() 可查询。queued signal、posted event 和 timer 在亲和线程事件循环处理:

text
sender any thread -> post event -> receiver affinity thread loop -> slot/event
1

直接从其他线程调用普通成员函数不会自动排队;只有 signal/invokeMethod 等明确 queued 机制才切换执行上下文。

moveToThread 的前置条件 ​

对象有 parent 时不能单独移动;移动 parent 会让 children 作为树一起保持一致。对象的 active timer、socket notifier 和正在处理事件使迁移更复杂,应在未启动/静止状态配置 affinity。

推荐结构:

text
GUI creates QThread object (lives GUI thread)
GUI creates Worker(no parent)
Worker moveToThread(thread)
thread.started -> Worker::start (queued)
Worker::finished -> thread.quit
thread.finished -> Worker::deleteLater
GUI waits thread on shutdown
1
2
3
4
5
6
7

QThread 子类的常见误解 ​

QThread 实例的 slots 默认在对象亲和线程(常为 GUI)运行,只有 run() 函数体在新线程。把 worker 状态作为 QThread 子类成员后从 GUI slot 与 run 同时访问,仍需同步。若只是工作对象,worker-object 模式更清楚。

事件循环与嵌套循环 ​

模态对话框、同步等待可能启动嵌套 event loop,使原本以为“函数返回前不会发生”的 deleteLater、signal 和输入重入。业务状态修改应能承受事件重入,避免用 processEvents() 修复卡顿。

生命周期关闭图 ​

text
stop new commands
 -> queued requestStop to worker
 -> worker cancels bounded operation, emits finished
 -> thread quit
 -> wait/join
 -> delete worker/thread/controller
1
2
3
4
5
6

不要在线程仍运行时销毁 QThread,也不要在 GUI 析构后让 worker 信号回调已死 UI。

连续追问与自测 ​

问:QObject parent 与 C++ 继承有关系吗? 答:没有,它是运行时对象树所有权关系。

问:moveToThread 后成员调用自动去新线程吗? 答:不会,普通直接调用仍在调用线程执行;queued 事件/信号才由亲和线程处理。

  1. QPointer 是否拥有对象? 答:不拥有,只在 QObject 销毁时清空。
  2. deleteLater 何时删除? 答:对象亲和线程事件循环处理 DeferredDelete 时。
  3. 为什么 shutdown 要 wait QThread? 答:确保 worker 不再访问即将销毁的状态与回调目标。

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

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

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

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

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

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

text
input -> parent/child -> deleteLater -> 线程亲和性 -> observable result
1

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

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

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

工程切片 2:接口边界 ​

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

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

text
input -> deleteLater -> 线程亲和性 -> 事件循环 -> 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 成本和工具链配置成本。

**边界。**先判断这里讨论的是 悬空指针、生命周期日志 还是 parent/child 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 悬空指针 -> 生命周期日志 -> parent/child -> observable result
1

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

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

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

工程切片 6:生命周期 ​

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

**边界。**先判断这里讨论的是 生命周期日志、parent/child 还是 deleteLater 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 生命周期日志 -> parent/child -> deleteLater -> observable result
1

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

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

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

工程切片 7:诊断证据 ​

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

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

text
input -> parent/child -> deleteLater -> 线程亲和性 -> observable result
1

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

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

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

工程切片 8:维护策略 ​

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

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

text
input -> deleteLater -> 线程亲和性 -> 事件循环 -> observable result
1

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

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

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

工程切片 9:版本演进 ​

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

**边界。**先判断这里讨论的是 线程亲和性、事件循环 还是 悬空指针 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 线程亲和性 -> 事件循环 -> 悬空指针 -> observable result
1

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

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

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

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

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

**边界。**先判断这里讨论的是 事件循环、悬空指针 还是 生命周期日志 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 事件循环 -> 悬空指针 -> 生命周期日志 -> observable result
1

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

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

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

工程切片 11:边界复盘 ​

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

**边界。**先判断这里讨论的是 悬空指针、生命周期日志 还是 parent/child 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 悬空指针 -> 生命周期日志 -> parent/child -> observable result
1

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

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

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

工程切片 12:反例训练 ​

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

**边界。**先判断这里讨论的是 生命周期日志、parent/child 还是 deleteLater 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 生命周期日志 -> parent/child -> deleteLater -> observable result
1

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

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

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

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

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

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

text
input -> parent/child -> deleteLater -> 线程亲和性 -> observable result
1

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

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

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

工程切片 14:接口边界 ​

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

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

text
input -> deleteLater -> 线程亲和性 -> 事件循环 -> 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 成本和工具链配置成本。

**边界。**先判断这里讨论的是 悬空指针、生命周期日志 还是 parent/child 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 悬空指针 -> 生命周期日志 -> parent/child -> observable result
1

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

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

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

工程切片 18:生命周期 ​

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

**边界。**先判断这里讨论的是 生命周期日志、parent/child 还是 deleteLater 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 生命周期日志 -> parent/child -> deleteLater -> observable result
1

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

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

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

工程切片 19:诊断证据 ​

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

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

text
input -> parent/child -> deleteLater -> 线程亲和性 -> observable result
1

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

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

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

工程切片 20:维护策略 ​

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

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

text
input -> deleteLater -> 线程亲和性 -> 事件循环 -> observable result
1

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

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

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

工程切片 21:版本演进 ​

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

**边界。**先判断这里讨论的是 线程亲和性、事件循环 还是 悬空指针 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 线程亲和性 -> 事件循环 -> 悬空指针 -> observable result
1

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

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

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

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

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

**边界。**先判断这里讨论的是 事件循环、悬空指针 还是 生命周期日志 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 事件循环 -> 悬空指针 -> 生命周期日志 -> observable result
1

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

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

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

工程切片 23:边界复盘 ​

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

**边界。**先判断这里讨论的是 悬空指针、生命周期日志 还是 parent/child 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 悬空指针 -> 生命周期日志 -> parent/child -> observable result
1

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

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

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

本篇收束 ​

掌握《QObject 生命周期与线程亲和性》的标志,不是记住所有名词,而是能把 parent/child、deleteLater、线程亲和性、事件循环、悬空指针、生命周期日志 放进一条可验证的工程链路。 先用最小样例建立判断,再用测试和脚本固定判断,最后把失败证据留给未来的自己和团队。

最后更新于:

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

持续记录,持续成长

Copyright © Tidenflow