9.5 KiB
DateItemModel 改进与修改 —— 再 Review
本文档对
doc/DateItemModel改进分析.md与doc/DateItemModel修改总结.md描述的改造工作进行交叉核对,比对实际代码(scene/ViewModels.h/.cpp、DateVarSet.cpp、scene/DateReplaceEditor.cpp、scene/BarcodeEditor.cpp、scene/ItemCommon.cpp/.h),列出确实落地、漏改、以及隐藏风险的部分。
1. 总体评估
文档描述的整体改造方向(QStringList → std::array<QString,N>、移除 createDefaultReplaceModel、新增默认构造函数、QMap<int,QString> yearsMap → std::array<QString,16> yearsArray + int startYear)在以下文件里基本落地:
scene/ViewModels.h:成员类型替换 ✅;声明默认构造函数 ✅;移除createDefaultReplaceModel✅。scene/ViewModels.cpp:默认构造函数初始化各数组 ✅;loadFromXml/saveToXml改为新结构 ✅(有缺陷,见 §2.2)。scene/DateReplaceEditor.cpp:dateModel()/setDateModel()改为循环复制 ✅。scene/BarcodeEditor.cpp:默认值兜底逻辑、模型搬运改为新结构 ✅。scene/ItemCommon.cpp:年份访问改为startYear + index✅。
但存在 1 处编译错误 与 1 处数据丢失隐患,外加若干小瑕疵。
2. 严重问题(必须修复)
2.1 🔴 DateVarSet.cpp:540 编译失败 —— 修改总结里漏改的关键一行
// DateVarSet.cpp 当前代码
yearList = model.yearsArray; // ❌ QStringList 不能直接 = std::array<QString,16>
yearList 是 DateVarSet.h 中声明的 QStringList;model.yearsArray 是 std::array<QString, 16>。两者之间没有赋值运算符,这一行直接编译错误。
scene/DateReplaceEditor.cpp 的对应位置已经改成了循环写入,但 DateVarSet.cpp 对此漏掉。
修复(与同文件 510~534 行其它 list 保持一致):
yearList.clear();
for (size_t i = 0; i < model.yearsArray.size(); ++i) {
yearList.append(model.yearsArray[i]);
}
影响面:编译过不去,整个 Qt 工程构建失败。
2.2 🔴 loadFromXml 没还原 startYear —— 跨年加载会丢用户配置
scene/ViewModels.cpp::loadFromXml 的相关片段:
m.startYear = QDate::currentDate().year(); // ← 永远用当前年份当 startYear
auto yearsMap = attrs.value("yearsMap").toString().split("<br>");
for (auto it = yearsMap.begin(); it != yearsMap.end(); ++it) {
auto yearPair = it->split("<subbr>");
int year = yearPair[0].toInt();
if (year >= m.startYear && year < m.startYear + 16) { // ← 早于当前年的全部丢弃
int index = year - m.startYear;
m.yearsArray[index] = yearPair[1];
}
}
而 saveToXml 是按 m.startYear + i 写出去的:
for (int i = 0; i < 16; ++i) {
int year = m.startYear + i;
yearsMap.append(QString("%1<subbr>%2<br>").arg(year).arg(m.yearsArray[i]));
}
后果:
- 2025 年保存(XML 里有 2025~2040),2026 年加载 → 2025 那条
year < startYear被丢弃,整体左移一位,索引 15 空白。 - 2024 年保存,2026 年加载 → 2024、2025 两条全丢,错位 2 位。
- 用户任何针对早于"系统当前年"的自定义年份映射,下次加载都会消失。
修复(推荐):把 startYear 作为独立 XML 属性持久化:
// saveToXml
attrs.append("startYear", QString::number(m.startYear));
// loadFromXml
int sy = attrs.value("startYear").toInt();
m.startYear = (sy != 0) ? sy : QDate::currentDate().year(); // 兼容旧文件
或退而求其次,loadFromXml 用 XML 里第一条 year 设定 m.startYear。
影响面:用户在编辑器里配置的年份字串,跨年使用会静默丢失/错位。
3. 次要问题(不影响编译,建议修)
3.1 🟡 scene/ItemCommon.h 头注释未同步
* - yyyy: 四位数年份,支持替换为自定义字符串(通过 model.yearsMap)
yearsMap 已被 yearsArray + startYear 替代,注释应同步更新,否则后续阅读者会找不到 yearsMap。
3.2 🟡 dateModel() 写入年份时丢了"用户起始年"语义
DateVarSet.cpp::dateModel() 与 scene/DateReplaceEditor.cpp::dateModel() 都是这种写法:
DateItemModel model; // model.startYear = 当前年(来自默认构造函数)
for (int i = 0; i < yearInitList.size() && i < 16; ++i) {
int year = yearInitList[i];
if (year >= model.startYear && year < model.startYear + 16) {
int index = year - model.startYear;
model.yearsArray[index] = yearList[i];
}
}
由于当前 yearInitList 也是从今年开始填充的,当前能凑巧跑通;但这里没有把 UI 里实际看到的起始年同步回 model.startYear。一旦未来扩展为"展示历史年份"或"展示从某指定年开始的年份序列",这里会静默丢数据。
更直白的写法:
if (!yearInitList.isEmpty()) {
model.startYear = yearInitList.first();
}
const int n = std::min(yearInitList.size(), 16);
for (int i = 0; i < n; ++i) {
model.yearsArray[i] = yearList[i];
}
3.3 🟡 signed/unsigned 比较警告
DateVarSet.cpp / scene/DateReplaceEditor.cpp 中大量这样写:
for (size_t i = 0; i < std::min(dataList.size(), static_cast<int>(model.dataMap.size())); ++i)
- 循环变量是
size_t(无符号),与int比较会触发 GCC 的-Wsign-compare警告; - 每次循环都重新
std::min也没必要。
建议改为:
const int n = std::min<int>(dataList.size(), static_cast<int>(model.dataMap.size()));
for (int i = 0; i < n; ++i) { ... }
3.4 🟡 scene/BarcodeEditor.cpp 五个手写循环冗余
for (size_t i = 0; i < m_vm.dateViewModel.dataMap.size(); ++i) {
m_vm.dateViewModel.dataMap[i] = rep.dataMap[i];
}
// ... 类似 5 段 ...
m_vm.dateViewModel.yearsArray = rep.yearsArray; // 这里直接赋值是对的
std::array 自身就支持 =,最后一行 yearsArray 已经这样写了。前面 5 段 for 循环都可以直接简化:
m_vm.dateViewModel.dataMap = rep.dataMap;
m_vm.dateViewModel.daysMap = rep.daysMap;
m_vm.dateViewModel.monthsMap = rep.monthsMap;
m_vm.dateViewModel.weeksMap = rep.weeksMap;
m_vm.dateViewModel.hoursMap = rep.hoursMap;
不影响功能,纯属冗余。
4. 文档与实现不一致清单
| 文档原话 | 实际代码 | 影响 |
|---|---|---|
| "DateVarSet.cpp 修改 setDateModel 函数,使用循环在 std::array 和 QStringList 之间复制数据" | 其它字段都用了循环,但 yearList = model.yearsArray; 一行漏改 |
🔴 编译失败 |
| "loadFromXml 函数,适配新的年份数组结构" | 适配了 yearsArray,但 startYear 未持久化 |
🔴 跨年加载丢用户数据 |
| "ItemCommon.cpp 更新了年份映射访问逻辑,适配新的 yearsArray 和 startYear 结构" | .cpp 已改,但 .h 头注释仍引用 yearsMap |
🟡 文档失同步 |
| "保持了兼容性:接口不变,仅内部实现改变" | XML schema 已变(yearsMap 编码方式变化、缺少 startYear 字段) |
🔴 与已部署设备的存档不完全互通 |
5. 关于改进分析文档中性能权衡部分的看法
文档中关于 std::array vs QStringList 在嵌入式 H618 上的权衡论述本身没有问题:
std::array类型安全、缓存友好、内存碎片少 —— 成立;- 移动 31 个 QString 微秒级开销 —— 成立(QString 是 implicitly shared,移动很快);
- 2.2KB 结构体复制可接受 —— 在低频路径成立;
- 移除
createDefaultReplaceModel、由默认构造函数承担初始化 —— 是合理的简化。
但有两点提醒:
-
std::array<QString,N>移动 ≠ "transfer pointer" 的 O(1)。每个 QString 元素逐个移动,引用计数不变但仍要逐个调用移动构造,总体 O(N)。N=31 时不是性能问题,但与文档"只需转移指针和引用计数"那一段的描述不太一致。 -
2.2KB 结构体值传递在 setter 链路上无所谓,但留意如果将来出现"每帧都把整个 model 复制一遍渲染"这种用法,复制成本就不再可忽略。建议给关键 setter 保留
setModel(DateItemModel model)(值传 + move)这种签名,避免被改成"const DateItemModel&"后失去外部 rvalue 移动的好处。
6. 结论
- 建议至少修掉 §2.1 与 §2.2:前者不修无法编译;后者不修跨年加载会丢用户在编辑器里配置的年份字串,且无任何运行时报错(最危险的那种 bug)。
- §3 的几条都是清理性工作,可按需进行。
- §4 中关于"接口不变 = 完全兼容"的说法在 XML schema 层面其实已不成立,老存档与新固件的双向兼容性需要单独评估。
7. 修复 Patch 摘要(建议改动)
// DateVarSet.cpp (around line 540)
- yearList = model.yearsArray;
+ yearList.clear();
+ for (size_t i = 0; i < model.yearsArray.size(); ++i) {
+ yearList.append(model.yearsArray[i]);
+ }
// scene/ViewModels.cpp saveToXml
+ attrs.append("startYear", QString::number(m.startYear));
// scene/ViewModels.cpp loadFromXml
- m.startYear = QDate::currentDate().year();
+ {
+ int sy = attrs.value("startYear").toInt();
+ m.startYear = (sy != 0) ? sy : QDate::currentDate().year();
+ }
// scene/ItemCommon.h 注释更新
- * - yyyy: 四位数年份,支持替换为自定义字符串(通过 model.yearsMap)
+ * - yyyy: 四位数年份,支持替换为自定义字符串(通过 model.yearsArray + model.startYear)