@chengzhao-dev/dsh-cpp-workflow 0.6.10 → 0.6.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.en.md +2 -2
  2. package/README.md +2 -2
  3. package/package.json +1 -1
  4. package/skills/building-cpp/assets/config/.clang-tidy +5 -3
  5. package/skills/building-cpp/assets/templates/projects/multi/CMakeLists.txt +1 -1
  6. package/skills/building-cpp/assets/templates/projects/multi/src/greeting.cpp +1 -1
  7. package/skills/building-cpp/assets/templates/projects/multi/src/main.cpp +1 -1
  8. package/skills/building-cpp/assets/templates/projects/shared-library/CMakeLists.txt +0 -1
  9. package/skills/building-cpp/assets/templates/projects/shared-library/greeting/CMakeLists.txt +1 -1
  10. package/skills/building-cpp/assets/templates/projects/shared-library/greeting/src/greeting.cpp +1 -1
  11. package/skills/building-cpp/assets/templates/projects/shared-library/src/main.cpp +1 -1
  12. package/skills/building-cpp/assets/templates/projects/static-library/CMakeLists.txt +0 -1
  13. package/skills/building-cpp/assets/templates/projects/static-library/greeting/CMakeLists.txt +1 -1
  14. package/skills/building-cpp/assets/templates/projects/static-library/greeting/src/greeting.cpp +1 -1
  15. package/skills/building-cpp/assets/templates/projects/static-library/src/main.cpp +1 -1
  16. package/skills/building-cpp/examples/tooling.md +18 -0
  17. package/skills/building-cpp/references/standards/c++17/README.md +2 -1
  18. package/skills/building-cpp/references/standards/c++20/README.md +1 -0
  19. package/skills/configuring-cmake/references/cmake.md +2 -2
  20. package/skills/developing-cpp/examples/structure.md +1 -1
  21. package/skills/developing-cpp/references/declaration-order.md +3 -2
  22. package/skills/developing-cpp/references/file-template.md +3 -3
  23. package/skills/developing-cpp/references/google-cpp-style.md +1 -1
  24. package/skills/developing-cpp/references/layout.md +1 -1
  25. package/skills/developing-cpp/references/project-design.md +1 -0
  26. package/skills/developing-cpp/references/types.md +9 -6
  27. package/skills/developing-cpp/steps/02-boundary.md +1 -1
  28. package/skills/developing-cpp/steps/04-implementation.md +1 -1
package/README.en.md CHANGED
@@ -19,13 +19,13 @@ The exception material follows the C++ Core Guidelines' Error handling section a
19
19
  Pinned npm install:
20
20
 
21
21
  ```bash
22
- dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.10
22
+ dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.11
23
23
  ```
24
24
 
25
25
  The matching Git tag:
26
26
 
27
27
  ```bash
28
- dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.10
28
+ dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.11
29
29
  ```
30
30
 
31
31
  The desktop app owns the `desktop` profile. If the command-line `add` is rejected, add the plugin in the app settings and use the same pinned npm version. A Git tag install uses the prebuilt `lib/` committed in the repository and runs no build on the target machine; the package has no install script that needs approval.
package/README.md CHANGED
@@ -19,13 +19,13 @@
19
19
  npm 安装钉死版本:
20
20
 
21
21
  ```bash
22
- dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.10
22
+ dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.11
23
23
  ```
24
24
 
25
25
  同版本的 Git 标签:
26
26
 
27
27
  ```bash
28
- dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.10
28
+ dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.11
29
29
  ```
30
30
 
31
31
  桌面应用独占 `desktop` profile。命令行 `add` 被拒绝时,在应用设置里添加插件,地址填同一个 npm 版本。Git 标签安装使用仓库内已构建的 `lib/`,不在目标机器上重新构建;包内也不含需要授权执行的安装脚本。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chengzhao-dev/dsh-cpp-workflow",
3
- "version": "0.6.10",
3
+ "version": "0.6.11",
4
4
  "description": "写 C++ 源码、配 CMake、用 Clang 构建;头文件用 /// Doxygen 文档注释,函数体用 // 分段说明,文件头标签可在设置里改。",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -54,9 +54,11 @@ CheckOptions:
54
54
  value: CamelCase
55
55
  - key: readability-identifier-naming.GlobalConstantPrefix
56
56
  value: 'k'
57
+ # 局部常量按变量命名:Google 只给静态存储期的常量加 k 前缀。
57
58
  - key: readability-identifier-naming.LocalConstantCase
58
- value: CamelCase
59
- - key: readability-identifier-naming.LocalConstantPrefix
60
- value: 'k'
59
+ value: lower_case
61
60
  - key: readability-identifier-naming.MacroDefinitionCase
62
61
  value: UPPER_CASE
62
+ # 包含守卫按 Google 规则以 _H_ 结尾,UPPER_CASE 会把它判成多余下划线。
63
+ - key: readability-identifier-naming.MacroDefinitionIgnoredRegexp
64
+ value: '^[A-Z][A-Z0-9_]*_H_$'
@@ -10,7 +10,7 @@ add_executable(app
10
10
  src/main.cpp
11
11
  src/greeting.cpp
12
12
  )
13
- target_include_directories(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
13
+ target_include_directories(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
14
14
  target_compile_features(app PRIVATE cxx_std_23)
15
15
  set_target_properties(app PROPERTIES
16
16
  CXX_STANDARD 23
@@ -1,4 +1,4 @@
1
- #include "include/greeting.h"
1
+ #include "greeting.h"
2
2
 
3
3
  #include <string>
4
4
  #if __cplusplus >= 202002L
@@ -1,6 +1,6 @@
1
1
  #include <iostream>
2
2
 
3
- #include "include/greeting.h"
3
+ #include "greeting.h"
4
4
 
5
5
  int main() {
6
6
  std::cout << greeting::MakeGreeting("C++23") << '\n';
@@ -10,7 +10,6 @@ enable_testing()
10
10
  add_subdirectory(greeting)
11
11
 
12
12
  add_executable(app src/main.cpp)
13
- target_include_directories(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
14
13
  target_compile_features(app PRIVATE cxx_std_23)
15
14
  set_target_properties(app PROPERTIES
16
15
  CXX_STANDARD 23
@@ -1,5 +1,5 @@
1
1
  add_library(greeting SHARED src/greeting.cpp)
2
- target_include_directories(greeting PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..)
2
+ target_include_directories(greeting PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
3
3
  target_compile_features(greeting PUBLIC cxx_std_23)
4
4
  set_target_properties(greeting PROPERTIES
5
5
  CXX_STANDARD 23
@@ -1,4 +1,4 @@
1
- #include "greeting/include/greeting.h"
1
+ #include "greeting.h"
2
2
 
3
3
  #include <string>
4
4
  #if __cplusplus >= 202002L
@@ -1,6 +1,6 @@
1
1
  #include <iostream>
2
2
 
3
- #include "greeting/include/greeting.h"
3
+ #include "greeting.h"
4
4
 
5
5
  int main() {
6
6
  std::cout << greeting::MakeGreeting("C++23") << '\n';
@@ -10,7 +10,6 @@ enable_testing()
10
10
  add_subdirectory(greeting)
11
11
 
12
12
  add_executable(app src/main.cpp)
13
- target_include_directories(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
14
13
  target_compile_features(app PRIVATE cxx_std_23)
15
14
  set_target_properties(app PROPERTIES
16
15
  CXX_STANDARD 23
@@ -1,5 +1,5 @@
1
1
  add_library(greeting STATIC src/greeting.cpp)
2
- target_include_directories(greeting PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..)
2
+ target_include_directories(greeting PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
3
3
  target_compile_features(greeting PUBLIC cxx_std_23)
4
4
  set_target_properties(greeting PROPERTIES
5
5
  CXX_STANDARD 23
@@ -1,4 +1,4 @@
1
- #include "greeting/include/greeting.h"
1
+ #include "greeting.h"
2
2
 
3
3
  #include <string>
4
4
  #if __cplusplus >= 202002L
@@ -1,6 +1,6 @@
1
1
  #include <iostream>
2
2
 
3
- #include "greeting/include/greeting.h"
3
+ #include "greeting.h"
4
4
 
5
5
  int main() {
6
6
  std::cout << greeting::MakeGreeting("C++23") << '\n';
@@ -168,3 +168,21 @@ clang++ 18.1.8, clang-format 18.1.8, clang-tidy 18.1.8, clangd 18.1.8
168
168
  ```
169
169
 
170
170
  同主版本;`--live` 会核对。Apple Clang 与 Homebrew 工具不同版本时,仍按同一 LLVM 主版本要求处理,不要强行降级或改声明绕过。
171
+
172
+ ## 把标准差异当成检查项
173
+
174
+ **差**:
175
+
176
+ ```yaml
177
+ # .clang-tidy
178
+ Checks: 'google-*,misc-*'
179
+ ```
180
+
181
+ **好**:
182
+
183
+ ```yaml
184
+ # .clang-tidy
185
+ Checks: '-*,google-*,readability-identifier-naming'
186
+ ```
187
+
188
+ 能不能写 `= default` 的比较运算符由语言标准决定,不是检查项的问题。`misc-non-private-member-variables-in-classes` 只在打开 `misc-*` 时出现;工作流基线不开这一族,工程配置要与基线相同,而不是给这条检查加忽略选项。
@@ -28,6 +28,7 @@
28
28
 
29
29
  - 没有 concepts、ranges、`std::format`、`std::span`、`std::bit_cast`。
30
30
  - 没有 `std::print`、`std::expected`、`std::stacktrace`。
31
+ - 没有 `= default` 的比较运算符。
31
32
  - `std::filesystem` 在 GCC 8 需要额外链接 `stdc++fs`,GCC 9 起才稳定;不要把“语言核心达标”当成文件系统库也达标。
32
33
 
33
34
  ## CMake
@@ -42,7 +43,7 @@
42
43
 
43
44
  ## 降档/升级
44
45
 
45
- 从 C++20 降到这里时,把 concepts 改成 `enable_if`/重载,把 ranges 改成迭代器算法,把 `std::format` 改成 `std::ostringstream`。升级到 C++20 后先确认 `std::format` 的库门槛(libstdc++ 13+ / libc++ 14+),不要只看编译器版本。
46
+ 从 C++20 降到这里时,把 concepts 改成 `enable_if`/重载,把 ranges 改成迭代器算法,把 `std::format` 改成 `std::ostringstream`,把 `= default` 的比较运算符改回结构体外的手写自由函数。升级到 C++20 后先确认 `std::format` 的库门槛(libstdc++ 13+ / libc++ 14+),不要只看编译器版本。
46
47
 
47
48
  ## 验证
48
49
 
@@ -22,6 +22,7 @@
22
22
  - C++17 的全部内容。
23
23
  - concepts、requires、ranges、`std::span`、`std::bit_cast`、`std::source_location`。
24
24
  - `std::format`、协程、`std::jthread`、三路比较。
25
+ - 按成员逐项比较时写成 `bool operator==(const T&) const = default`。C++23 沿用,不另写一遍。
25
26
  - 模块语法(工具链支持不完整,模板不使用)。
26
27
 
27
28
  ## 本档不能假设
@@ -54,12 +54,12 @@ target_link_libraries(app PRIVATE greeting)
54
54
  ```cmake
55
55
  # greeting/CMakeLists.txt
56
56
  add_library(greeting STATIC src/greeting.cpp)
57
- target_include_directories(greeting PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..)
57
+ target_include_directories(greeting PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
58
58
  CompileSettings_SetCxxStandard(greeting PUBLIC c++23)
59
59
  CompileSettings_SetWarnings(greeting PRIVATE LEVEL standard)
60
60
  ```
61
61
 
62
- - 库的 `PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..` 让程序能从仓库根写 `#include "greeting/include/greeting.h"`,不需要程序再写 include 路径。
62
+ - 库的 `PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include` 让程序与链接它的测试写 `#include "greeting.h"` 就能找到公共头,不需要各自再写 include 路径。
63
63
  - 标准用 `CompileSettings_SetCxxStandard`:库用 `PUBLIC` 传播,程序用 `PRIVATE`;函数内部同时写 `target_compile_features` 与 `CXX_STANDARD` 三属性。
64
64
  - `CXX_STANDARD_REQUIRED ON` 防止 CMake 在编译器不支持时静默 decay;`CXX_EXTENSIONS OFF` 把 `-std=gnu++NN` 收成 `-std=c++NN`。configure 后检查 `compile_commands.json` 的最后一条 `-std=`。
65
65
  - 程序 target 固定 `app`;库 target 用组件名。
@@ -68,7 +68,7 @@ task_list/src/detail.h // 私有头,只被本组件包含
68
68
  ```
69
69
 
70
70
  ```cpp
71
- #include "task_list/include/task_list.h" // 从源码根写公共头的完整路径
71
+ #include "task_list.h" // 公共头按裸文件名包含
72
72
  ```
73
73
 
74
74
  构建时把仓库根加入 include 搜索路径;不要用 `#include "../task_list/include/task_list.h"` 这种随文件位置漂移的写法。
@@ -45,8 +45,9 @@ class TaskTable {
45
45
 
46
46
  Google、LLVM、OpenCV 都没有规定这一组内部的次序;本工作流补上:
47
47
 
48
- - 谓词 `Is` / `Has` / `Can`,然后 `Get`,然后普通函数按使用先后,最后是 `friend` 函数。
49
- - `friend` 不放在这一组开头,也不单放在类的最前面,不强制放进 `private`。
48
+ - 开头是 `operator==`:成员默认比较和 `friend` 都放这里,然后才是谓词 `Is` / `Has` / `Can`、`Get`、普通函数按使用先后。`friend` 不再放到这一组末尾。
49
+ - 比较运算符的签名与 `[[nodiscard]]` 规则见 `references/types.md`,这里不另写一套。
50
+ - 同一类型上的谓词留在类内,不因为实现只转调公开函数就拆成类外自由函数。
50
51
  - `friend` 类型仍跟其他类型放在一起,和 `friend` 函数不是同一组。
51
52
 
52
53
  ## 命名空间作用域的头文件
@@ -57,7 +57,7 @@
57
57
  #include <cstdint>
58
58
  #include <vector>
59
59
 
60
- #include "project/path/dependency.h"
60
+ #include "dependency.h"
61
61
 
62
62
  namespace project {
63
63
 
@@ -101,11 +101,11 @@ class ThingTable {
101
101
  /// @file thing_table.cpp
102
102
  /// @brief 实现 ThingTable 的添加与查询。
103
103
 
104
- #include "project/path/thing.h"
104
+ #include "thing.h"
105
105
 
106
106
  #include <algorithm>
107
107
 
108
- #include "project/path/other.h"
108
+ #include "other.h"
109
109
 
110
110
  namespace project {
111
111
 
@@ -16,7 +16,7 @@
16
16
  - 头文件必须自包含:自己用到的类型、宏、标准库头都直接包含,不依赖调用方的包含顺序。
17
17
  - `.cpp` 第一个包含自己的配对头,然后按下面的顺序包含。
18
18
  - 包含顺序:配对头 → C 系统头 → C++ 标准库头 → 其他库头 → 本项目头。每组之间空一行,组内按字母序;不要依赖 `clang-format` 的 `IncludeCategories` 自定义分组,本项目不再覆盖 Google 预设。
19
- - 本项目头用引号,路径从项目源码根开始,例如 `#include "base/logging.h"`。构建时把源码根加入 include 搜索路径,不要只把组件的 `include/` 设为根再写裸文件名。
19
+ - 本项目头用引号写裸文件名,例如 `#include "logging.h"`。构建时把该组件的 `include/` 加入 include 搜索路径(`target_include_directories(<target> PUBLIC .../include)`),不要写 `"base/logging.h"` 这种从源码根起的长路径。
20
20
  - 公共头文件直接放在组件的 `include/` 下;不要为了「整齐」再加一层命名空间目录。
21
21
  - 跨文件共享的常量用 `inline constexpr`,放在命名空间里并带 `k` 前缀;头文件里不要定义非 inline 的普通全局变量。
22
22
  - 头文件里只放模板、`constexpr`、类内联小函数和声明;普通函数的函数体放 `.cpp`。
@@ -15,7 +15,7 @@
15
15
  <name>_test.cpp 通过公共接口测试
16
16
  ```
17
17
 
18
- - 公共头直接放 `include/` 下;包含时从仓库根写完整路径,例如 `#include "task_list/include/task_list.h"`,不要写裸文件名或 `../`。
18
+ - 公共头直接放 `include/` 下,包含时写裸文件名,例如 `#include "task_list.h"`;不要写 `"task_list/include/task_list.h"` 这种从仓库根起的长路径,也不要写 `../` 相对路径。
19
19
  - 私有头放 `src/`,不进入公共 include 根;测试不得包含私有头。
20
20
  - 一个组件一个职责;组件名用 snake_case,和命名空间一致。
21
21
  - 组件之间通过公共头依赖;不要用 `../other/src/private.h` 跨组件包含私有实现。
@@ -26,6 +26,7 @@
26
26
  - 一次写入能区分的失败都放进结果枚举。传入空取值是结果项,不抛异常。
27
27
  - 只由某一个类型产生的结果枚举嵌在该类型里:格子的写入结果和对局的写入结果取值不同,不合成命名空间里的一个 `PlaceResult`。
28
28
  - 尺寸是某个类型的事实时,写成该类型的 `static constexpr`,例如 `Board::kRowCount`;不要再造一个只做 `static_cast<std::size_t>` 的别名常量。
29
+ - 一个局面的结果只用一个枚举和一个查询。不要把满盘、终局和赢家拆成三个入口,再由调用方拼回去。
29
30
 
30
31
  ## 封闭状态与结果
31
32
 
@@ -31,23 +31,25 @@ int CountWide(const std::vector<int>& values) {
31
31
  }
32
32
  ```
33
33
 
34
- 循环下标与容器大小都用 `std::size_t`,避免混合比较。业务编号另按下面的定宽约定。
34
+ 循环下标与容器大小都用 `std::size_t`,避免混合比较。业务编号与坐标另按下面的约定。
35
35
 
36
36
  ## 项目对整型角色的约定
37
37
 
38
38
  | 角色 | 类型 | 理由 |
39
39
  | --- | --- | --- |
40
- | 业务编号、尺寸、参与边界判定的下标 | `std::int32_t` | 能表达负值,避开 `size_t` 与负数比较的陷阱 |
41
- | 容器长度、`std::array` 长度、由编号换算的索引 | `std::size_t` | 与 STL 和 NTTP 一致 |
42
- | 封闭状态码的底层类型 | `std::uint8_t` | 只表达取值集合和布局,不参与算术 |
40
+ | 不能为负的尺寸、容器长度、`std::array` 长度 | `std::size_t` | 与 STL 和 NTTP 一致 |
41
+ | 坐标等允许表示范围外取值的下标 | `int` | 能表达负值;越界判断交给持有尺寸的类型 |
42
+ | 没有上面两种理由的整数 | `int` | 宽度足够,写起来最短 |
43
+ | 取值很少的 `enum class` 的底层类型 | `std::uint8_t` | 只表达取值集合和布局,不参与算术 |
43
44
 
45
+ - 协议、文件格式与跨平台布局仍用 `<cstdint>` 的定宽整数(如 `std::int32_t`);工程内普通代码不必写定宽类型。
44
46
  - 不用裸 `int` 承担跨平台协议或布局;它的宽度不可预测。
45
47
  - 不用 `std::uint8_t` / `std::uint16_t` 当业务编号;负值无法表达,整数提升还会制造意外。
46
48
  - 有符号乘法进入容器长度前先显式拓宽,否则会触发 `bugprone-implicit-widening-of-multiplication-result`:
47
49
 
48
50
  ```cpp
49
- inline constexpr std::int32_t kBatchSize = 3;
50
- inline constexpr std::int32_t kItemsPerBatch = 3;
51
+ inline constexpr int kBatchSize = 3;
52
+ inline constexpr int kItemsPerBatch = 3;
51
53
  inline constexpr std::size_t kItemCount =
52
54
  static_cast<std::size_t>(kBatchSize) * static_cast<std::size_t>(kItemsPerBatch);
53
55
  std::array<TaskState, kItemCount> items_{};
@@ -115,6 +117,7 @@ class TaskBoard {
115
117
  - 不给所有非 `void` 函数机械添加;标注的价值来自稀缺。
116
118
  - 写法位置:`[[nodiscard]]` 在返回类型前面,然后才是 `static` 或 `constexpr`;参数列表之后才是 `const`、`override`、`noexcept`。
117
119
  - 类内 `friend` 不加 `[[nodiscard]]`:Clang 拒绝 `[[nodiscard]] friend`、`friend [[nodiscard]]` 和参数列表后的 `[[nodiscard]]`。不要再猜下一种写法。
120
+ - 运算符不加 `[[nodiscard]]`,成员与 `friend` 都一样。比较的参数不具名:成员写 `bool operator==(const T&) const = default`,`friend` 写 `friend bool operator==(const T&, const T&);`;注释不给这个不具名参数写 `@param`。
118
121
  - 有意丢弃时写 `static_cast<void>(result)`,不要为此全局关闭警告。
119
122
  - 测试里验证“必须处理结果”时,同时验证忽略能被编译器发现。
120
123
 
@@ -15,7 +15,7 @@ app 只做参数、输入输出和装配
15
15
 
16
16
  - 库 target 不依赖终端、GUI、网络脚本或全局单例;它只接收值、返回值、在无法完成任务时抛异常。
17
17
  - 程序 target 链接库,负责读参数、读标准输入、打印结果、把退出码翻译成进程退出码。
18
- - 公共头文件直接放在组件的 `include/` 下,从仓库根写完整路径包含,例如 `#include "task_list/include/task_list.h"`;不要写裸文件名或 `../` 相对路径。
18
+ - 公共头文件直接放在组件的 `include/` 下,包含时写裸文件名,例如 `#include "task_list.h"`;由构建把该目录加入 include 搜索路径,不要写从仓库根起的长路径或 `../` 相对路径。
19
19
  - 依赖方向从程序指向库。库不反向包含程序的 `cli.h`,也不引用 `main.cpp`。
20
20
  - 测试放 `tests/`,测试 target 链接被测库;测试不复制库的实现。
21
21
  - 只有一个可执行文件、没有复用需求时,可以不分库;一旦出现第二个调用方或测试需要直接调用,再拆库。
@@ -17,7 +17,7 @@
17
17
  - 不写 C 风格强制转换;用 `static_cast` / `std::bit_cast` 等具名转换。
18
18
  - 所有权用 `std::unique_ptr`;没有共享所有权的理由时不用 `std::shared_ptr`。
19
19
  - 只改注释时,不改可观察行为;只改实现时,不改公共声明。两者同时需要时,先改声明,再改实现。
20
- - 清空成员数组:标准支持 ranges 时用 `std::ranges::fill(cells_ | std::views::join, CellType::kEmpty)`;不支持时用 `for` 循环逐格赋 `kEmpty`。两种都不写 `cells_ = {}`,那依赖枚举零值恰好是 `kEmpty`。
20
+ - 清空成员数组:标准支持 ranges 时用 `std::ranges::fill(cells_ | std::views::join, CellState::kEmpty)`;不支持时用 `for` 循环逐格赋 `kEmpty`。两种都不写 `cells_ = {}`,那依赖枚举零值恰好是 `kEmpty`。
21
21
  - 默认构造只做「建立初值」且与 `Clear()` 同义时,构造函数调用 `Clear()`,不另设 `Init()`;类内 `cells_{}` 仍保留,让成员先有确定初值。
22
22
 
23
23
  ## 情况