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

外观

本页目录

Model/View 正确性与性能 ​

1. Model 契约 ​

QAbstractItemModel 不是简单数组 adapter。view 依赖 index 的 row/column/parent、flags、data 和结构变化通知保持一致。

text
QModelIndex
  +-- row/column
  +-- model pointer
  +-- internalId/internalPointer(模型定义)
  +-- parent relationship(由模型回答)
1
2
3
4
5

普通 QModelIndex 是临时句柄,结构变化后可失效;长期需要用 QPersistentModelIndex,并正确发出变化通知让 Qt 更新它。

2. begin/end 必须包围真实变化 ​

cpp
beginInsertRows(parent, first, last);
storage.insert(...); // 只在 begin/end 之间改变结构
endInsertRows();
1
2
3

区间必须与实际插入数和 parent 完全一致。删除、移动、reset 同理。提前修改 storage 再 begin 会让 view 在通知期观察到不一致模型。

只改变值不改变结构时发 dataChanged(topLeft, bottomRight, roles),精确 roles/区域避免全视图刷新。

3. Reset 的代价 ​

beginResetModel/endResetModel 简单但会丢 selection、expanded state、persistent indexes 并导致大范围重建。增量变化能保持用户上下文;只有数据身份整体替换时 reset。

Layout change 适合重新排列但元素身份保留,需要按 Qt 协议更新 persistent indexes。能用 beginMoveRows 时优先更明确的移动通知。

4. Tree index 身份 ​

internalPointer 指向节点时,节点地址在 index 使用期必须稳定。vector<Node> 扩容可能使所有指针悬空;可使用稳定节点存储、ID 映射或 internalId。删除节点前必须发正确 remove 通知,且不能让 view 回调访问已释放节点。

5. Proxy model ​

排序/过滤优先 QSortFilterProxyModel,避免复制一份视图数据。任何 source/proxy index 转换都使用 mapToSource/mapFromSource,不能假定 row 相同。

自定义 lessThan 必须提供稳定严格排序,处理 invalid QVariant、NaN、locale 和不同类型。动态筛选频繁变化时批量 invalidate,避免每个字段变化全量排序。

6. 大数据与 fetchMore ​

百万行不意味着一次创建百万 QObject/QStandardItem。自定义 model 按需读取领域数据:

  • canFetchMore/fetchMore 分批加载;
  • data() 避免昂贵格式化和 I/O;
  • 缓存可见区域/格式结果并有失效策略;
  • 后台加载只生成纯数据快照,模型结构变化回 GUI 线程;
  • 批量发 dataChanged,不逐 cell 发信号。

7. Delegate ​

delegate paint 应无阻塞、少分配;editor 生命周期由 view/delegate 协议管理。setModelData 验证输入并通过 model setData 修改,让 undo/validation/通知保持统一。

8. Model tester ​

Qt Test 提供 QAbstractItemModelTester(版本需确认),可检查多类模型不变量。配合随机插入/删除/移动和 proxy/selection 测试,能发现 parent/index/notification 错误。

9. 面试速答 ​

什么时候用 dataChanged 而非 reset? 结构/身份未变,只是已存在 index 的角色值变化。

internalPointer 可指 vector 元素吗? 只有能保证元素地址稳定;扩容/移动会悬空,通常不安全。

Model 能否在 worker 线程直接修改? GUI view 使用的 model 通常应在 GUI 线程更新;worker 产生快照/结果,通过 queued 提交。

10. 自测答案 ​

beginInsertRows 的 first/last 是插入后的行区间还是旧存储?是即将插入的新行位置,且在 begin/end 之间实际改变。Proxy row 能否直接用于 source?不能,必须映射。

Model Index 是临时地址,不是业务对象所有权 ​

QModelIndex 由 model、row/column 与内部标识组成,通常只在模型结构未变化且模型仍活着时有效。不要长期存普通 index;需要跨结构更新时使用 QPersistentModelIndex,仍需正确 begin/end 通知。

text
view index -> model identity + logical position/internal id
model reset/destroy -> index invalid
1
2

rowCount、index、parent 必须形成一致树 ​

树模型对每个 parent 返回 child count,index(row,col,parent) 创建 child,parent(child) 必须返回对应父:

text
root
├── A
│   └── A1
└── B
1
2
3
4

错误 parent 循环、越界 row 或同一节点多个身份会让 view 递归、selection 和 persistent index 失效。

begin/end 修改协议 ​

text
beginInsertRows(parent, first,last)
 -> modify underlying storage exactly once
endInsertRows()
1
2
3

begin 前模型仍是旧状态,end 前存储应已是新状态。不要先修改再 begin;异常不能穿过已 begin 未 end 的窗口。复杂操作先准备数据,再进入不可失败的通知/提交区。

reset 是重锤 ​

beginResetModel/endResetModel 使所有 index/selection/cache 大范围失效,简单但破坏 UI 状态与增量性能。能精确 insert/remove/move/dataChanged 就不 reset;真正替换整个文档模型时 reset 合理。

dataChanged 范围与 roles ​

只通知实际变化的矩形与 roles,避免整表重绘。修改排序键还可能影响 proxy 顺序;模型和代理按 Qt 契约发 layout/rows 信号,不要只发 dataChanged 假装结构没变。

internalPointer 的生命周期 ​

若 index 保存节点指针,节点地址必须在 index 有效期稳定。vector 重分配、对象池复用和后台线程删除都会制造 stale pointer。可用稳定节点、ID/代次映射,或在结构更新中正确使 index 失效。

Proxy 链 ​

text
source model index
 -> sort/filter proxy index
 -> selection/view index
1
2
3

业务操作需要 mapToSource/mapFromSource 穿过每层;不要把 proxy row 当 source row。多个 proxy 叠加时封装映射函数并测试排序/过滤后编辑与 selection。

线程规则 ​

QAbstractItemModel 通常属于 GUI 线程,view 会同步调用其方法。worker 计算值副本/增量,随后 queued 到 model 亲和线程提交:

text
worker produces immutable batch
 -> queued invoke GUI
 -> model begin/modify/end
1
2
3

在 worker 直接发 beginInsertRows 并改 model 会与 view 并发访问。

连续追问与自测 ​

问:为什么不能修改数据后再 beginInsertRows? 答:view 要在 begin 时看到旧状态并准备 index 映射,顺序反了违反模型协议。

问:QPersistentModelIndex 永远有效吗? 答:不,model reset/destroy 或相关项删除仍会失效。

  1. proxy row 能直接索引 source 吗? 答:不能,必须映射。
  2. internalPointer 首要要求? 答:地址/身份在 index 有效期稳定。
  3. worker 如何更新模型? 答:计算独立结果,再 queued 到 model 线程提交。

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

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

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

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

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

**边界。**先判断这里讨论的是 index、role 还是 begin/end 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> index -> role -> begin/end -> observable result
1

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

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

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

工程切片 2:接口边界 ​

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

**边界。**先判断这里讨论的是 role、begin/end 还是 selection 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> role -> begin/end -> selection -> observable result
1

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

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

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

工程切片 3:失败注入 ​

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

**边界。**先判断这里讨论的是 begin/end、selection 还是 proxy model 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> begin/end -> selection -> proxy model -> observable result
1

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

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

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

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

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

**边界。**先判断这里讨论的是 selection、proxy model 还是 增量更新 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> selection -> proxy model -> 增量更新 -> observable result
1

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

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

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

工程切片 5:性能观察 ​

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

**边界。**先判断这里讨论的是 proxy model、增量更新 还是 index 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> proxy model -> 增量更新 -> index -> observable result
1

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

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

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

工程切片 6:生命周期 ​

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

**边界。**先判断这里讨论的是 增量更新、index 还是 role 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 增量更新 -> index -> role -> observable result
1

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

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

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

工程切片 7:诊断证据 ​

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

**边界。**先判断这里讨论的是 index、role 还是 begin/end 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> index -> role -> begin/end -> observable result
1

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

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

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

工程切片 8:维护策略 ​

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

**边界。**先判断这里讨论的是 role、begin/end 还是 selection 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> role -> begin/end -> selection -> observable result
1

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

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

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

工程切片 9:版本演进 ​

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

**边界。**先判断这里讨论的是 begin/end、selection 还是 proxy model 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> begin/end -> selection -> proxy model -> observable result
1

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

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

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

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

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

**边界。**先判断这里讨论的是 selection、proxy model 还是 增量更新 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> selection -> proxy model -> 增量更新 -> observable result
1

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

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

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

工程切片 11:边界复盘 ​

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

**边界。**先判断这里讨论的是 proxy model、增量更新 还是 index 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> proxy model -> 增量更新 -> index -> observable result
1

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

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

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

工程切片 12:反例训练 ​

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

**边界。**先判断这里讨论的是 增量更新、index 还是 role 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 增量更新 -> index -> role -> observable result
1

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

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

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

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

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

**边界。**先判断这里讨论的是 index、role 还是 begin/end 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> index -> role -> begin/end -> observable result
1

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

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

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

工程切片 14:接口边界 ​

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

**边界。**先判断这里讨论的是 role、begin/end 还是 selection 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> role -> begin/end -> selection -> observable result
1

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

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

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

工程切片 15:失败注入 ​

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

**边界。**先判断这里讨论的是 begin/end、selection 还是 proxy model 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> begin/end -> selection -> proxy model -> observable result
1

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

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

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

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

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

**边界。**先判断这里讨论的是 selection、proxy model 还是 增量更新 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> selection -> proxy model -> 增量更新 -> observable result
1

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

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

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

工程切片 17:性能观察 ​

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

**边界。**先判断这里讨论的是 proxy model、增量更新 还是 index 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> proxy model -> 增量更新 -> index -> observable result
1

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

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

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

工程切片 18:生命周期 ​

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

**边界。**先判断这里讨论的是 增量更新、index 还是 role 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 增量更新 -> index -> role -> observable result
1

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

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

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

工程切片 19:诊断证据 ​

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

**边界。**先判断这里讨论的是 index、role 还是 begin/end 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> index -> role -> begin/end -> observable result
1

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

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

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

工程切片 20:维护策略 ​

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

**边界。**先判断这里讨论的是 role、begin/end 还是 selection 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> role -> begin/end -> selection -> observable result
1

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

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

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

工程切片 21:版本演进 ​

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

**边界。**先判断这里讨论的是 begin/end、selection 还是 proxy model 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> begin/end -> selection -> proxy model -> observable result
1

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

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

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

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

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

**边界。**先判断这里讨论的是 selection、proxy model 还是 增量更新 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> selection -> proxy model -> 增量更新 -> observable result
1

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

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

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

工程切片 23:边界复盘 ​

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

**边界。**先判断这里讨论的是 proxy model、增量更新 还是 index 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> proxy model -> 增量更新 -> index -> observable result
1

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

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

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

工程切片 24:反例训练 ​

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

**边界。**先判断这里讨论的是 增量更新、index 还是 role 的责任;如果三者混在一起,先把输入、输出和所有权拆开。

text
input -> 增量更新 -> index -> role -> observable result
1

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

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

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

本篇收束 ​

掌握《Model/View 正确性与性能》的标志,不是记住所有名词,而是能把 index、role、begin/end、selection、proxy model、增量更新 放进一条可验证的工程链路。 先用最小样例建立判断,再用测试和脚本固定判断,最后把失败证据留给未来的自己和团队。

最后更新于:

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

持续记录,持续成长

Copyright © Tidenflow