@chengzhao-dev/dsh-cpp-workflow 0.0.0-stage → 0.1.1
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.
- package/LICENSE +21 -0
- package/README.en.md +106 -0
- package/README.md +105 -2
- package/cordis.patch.yml +11 -0
- package/icon.svg +7 -0
- package/lib/index.js +203 -0
- package/lib/workspace.js +168 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +80 -4
- package/skills/building-cpp/SKILL.md +44 -0
- package/skills/building-cpp/assets/cmake/applying-warnings.cmake +10 -0
- package/skills/building-cpp/assets/cmake/fetching-googletest.cmake +15 -0
- package/skills/building-cpp/assets/cmake/setting-rpath.cmake +8 -0
- package/skills/building-cpp/assets/config/.clang-format +40 -0
- package/skills/building-cpp/assets/config/.clang-tidy +92 -0
- package/skills/building-cpp/assets/config/.clangd +12 -0
- package/skills/building-cpp/assets/config/.vscode/extensions.json +6 -0
- package/skills/building-cpp/assets/config/.vscode/settings.json +34 -0
- package/skills/building-cpp/assets/templates/projects/common/running-build.sh +152 -0
- package/skills/building-cpp/assets/templates/projects/multi/CMakeLists.txt +18 -0
- package/skills/building-cpp/assets/templates/projects/multi/include/greeting.h +10 -0
- package/skills/building-cpp/assets/templates/projects/multi/src/greeting.cpp +13 -0
- package/skills/building-cpp/assets/templates/projects/multi/src/main.cpp +8 -0
- package/skills/building-cpp/assets/templates/projects/shared-library/CMakeLists.txt +24 -0
- package/skills/building-cpp/assets/templates/projects/shared-library/greeting/CMakeLists.txt +5 -0
- package/skills/building-cpp/assets/templates/projects/shared-library/greeting/include/greeting.h +10 -0
- package/skills/building-cpp/assets/templates/projects/shared-library/greeting/src/greeting.cpp +13 -0
- package/skills/building-cpp/assets/templates/projects/shared-library/src/main.cpp +8 -0
- package/skills/building-cpp/assets/templates/projects/single/CMakeLists.txt +14 -0
- package/skills/building-cpp/assets/templates/projects/single/src/main.cpp +9 -0
- package/skills/building-cpp/assets/templates/projects/static-library/CMakeLists.txt +18 -0
- package/skills/building-cpp/assets/templates/projects/static-library/greeting/CMakeLists.txt +5 -0
- package/skills/building-cpp/assets/templates/projects/static-library/greeting/include/greeting.h +10 -0
- package/skills/building-cpp/assets/templates/projects/static-library/greeting/src/greeting.cpp +13 -0
- package/skills/building-cpp/assets/templates/projects/static-library/src/main.cpp +8 -0
- package/skills/building-cpp/examples/cmake.md +150 -0
- package/skills/building-cpp/examples/environment.md +125 -0
- package/skills/building-cpp/examples/tooling.md +155 -0
- package/skills/building-cpp/references/build-script.md +63 -0
- package/skills/building-cpp/references/cmake.md +168 -0
- package/skills/building-cpp/references/environment.md +104 -0
- package/skills/building-cpp/references/linking.md +101 -0
- package/skills/building-cpp/references/static-analysis.md +136 -0
- package/skills/building-cpp/scripts/checking-environment.mjs +498 -0
- package/skills/building-cpp/steps/01-build.md +76 -0
- package/skills/building-cpp/steps/02-tooling.md +41 -0
- package/skills/building-cpp/steps/03-check.md +60 -0
- package/skills/developing-cpp/SKILL.md +42 -0
- package/skills/developing-cpp/examples/comments.md +231 -0
- package/skills/developing-cpp/examples/exceptions.md +134 -0
- package/skills/developing-cpp/examples/naming.md +157 -0
- package/skills/developing-cpp/examples/structure.md +79 -0
- package/skills/developing-cpp/references/comments.md +123 -0
- package/skills/developing-cpp/references/declaration-order.md +97 -0
- package/skills/developing-cpp/references/exceptions.md +79 -0
- package/skills/developing-cpp/references/glossary.md +88 -0
- package/skills/developing-cpp/references/google-cpp-style.md +109 -0
- package/skills/developing-cpp/references/language.md +119 -0
- package/skills/developing-cpp/references/layout.md +73 -0
- package/skills/developing-cpp/references/naming.md +62 -0
- package/skills/developing-cpp/references/performance.md +82 -0
- package/skills/developing-cpp/references/project-design.md +79 -0
- package/skills/developing-cpp/references/standard-library.md +100 -0
- package/skills/developing-cpp/references/types.md +147 -0
- package/skills/developing-cpp/steps/01-behavior.md +36 -0
- package/skills/developing-cpp/steps/02-boundary.md +40 -0
- package/skills/developing-cpp/steps/03-declaration.md +32 -0
- package/skills/developing-cpp/steps/04-implementation.md +33 -0
- package/templates/cpp-workflow-project/SKILL.md +49 -0
- package/templates/cpp-workflow-project/environment.json +5 -0
- package/templates/cpp-workflow-project/stages/stage-table.md +4 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# 注释
|
|
2
|
+
|
|
3
|
+
注释分四层:文件头、章节横幅、声明注释、实现注释。配置文件的注释单独一层。每层只写该层该管的事,不互相复制。
|
|
4
|
+
|
|
5
|
+
## 文件头
|
|
6
|
+
|
|
7
|
+
每个手写源文件、脚本、配置文件开头写一段文件头:这个文件负责什么、边界在哪里、什么时候需要读它。不要写版权年份、作者、变更日志。
|
|
8
|
+
|
|
9
|
+
```cpp
|
|
10
|
+
//===----------------------------------------------------------------------===//
|
|
11
|
+
// TaskTable 的存储、编号检查与状态更新规则。
|
|
12
|
+
//
|
|
13
|
+
// 不处理终端输入输出;程序层负责把用户输入翻译成 TaskId。
|
|
14
|
+
//===----------------------------------------------------------------------===//
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- C++ 文件用 `//`,CMake 用 `#`,YAML/JSON 用 `#` 或 `//`(按文件类型)。
|
|
18
|
+
- 文件头控制在 6 行以内;不写实现步骤,不写外部资料路径或项目专属类名。
|
|
19
|
+
- 生成文件、第三方文件、纯数据文件可以不写文件头。
|
|
20
|
+
|
|
21
|
+
## 章节横幅
|
|
22
|
+
|
|
23
|
+
长文件(超过约 200 行)用横幅分组,组名是读者要跳转的锚点。
|
|
24
|
+
|
|
25
|
+
```cpp
|
|
26
|
+
//===----------------------------------------------------------------------===//
|
|
27
|
+
// 构造与生命周期
|
|
28
|
+
//===----------------------------------------------------------------------===//
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- 横幅宽度固定 100 列;`//` 后 3 个 `=`,中间 90 个 `-`,末尾 `===//`。
|
|
32
|
+
- 横幅只包住逻辑组,不包住单个函数;函数之间用空行,不用横幅。
|
|
33
|
+
- 脚本里用 `#===` 形式,宽度同样 100 列;CMake 用 `#---` 小节即可,不用重横幅。
|
|
34
|
+
- 一个文件不超过 8 个横幅;超过说明文件该拆了。
|
|
35
|
+
- 100 列换行分三类:整句在标点处断;参数/返回值列表每项一行;代码示例保持原样,宁可横向滚动也不在半句断开。
|
|
36
|
+
|
|
37
|
+
## 声明注释
|
|
38
|
+
|
|
39
|
+
公共头文件里的类型、函数、常量用 `///` 写契约,调用方读完就知道怎么用。
|
|
40
|
+
|
|
41
|
+
```cpp
|
|
42
|
+
/// 返回任务编号对应的状态。
|
|
43
|
+
///
|
|
44
|
+
/// @param id 任务编号,必须在 [0, kTaskCount) 内
|
|
45
|
+
/// @return 该编号的任务状态
|
|
46
|
+
/// @throws std::out_of_range id 越界
|
|
47
|
+
[[nodiscard]] TaskState GetTaskState(int id) const;
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- 第一行一句话说清「做什么」,空一行后写参数、返回值、异常。
|
|
51
|
+
- `@param` 要么全写,要么全不写;只有一个参数且含义自明时可以省略。
|
|
52
|
+
- `@return` 在返回类型自明时省略;`[[nodiscard]]` 已经表达「必须使用」时不要再写一遍。
|
|
53
|
+
- `@throws` 必须和 `references/exceptions.md` 的合同一致;不抛的函数不要写。
|
|
54
|
+
- 禁止 `@todo`、`@fixme`、`@author`、`@since`;待办统一用下面的 TODO 格式。
|
|
55
|
+
- 枚举值的尾随注释用 `///<`,全部值都写或全部不写,并对齐;语义结果写在最前,编号或内部值写在后面。
|
|
56
|
+
- 私有声明同样写合同:调用者可能是同一个类的另一个成员,不能假设它知道所有前置条件。
|
|
57
|
+
- 参数是输出参数时在 `@param` 后标 `[out]`;既读又写时标 `[in,out]`。
|
|
58
|
+
- 不写 `@brief`、`@details` 这类空壳标签;第一行本身就是摘要。
|
|
59
|
+
|
|
60
|
+
## 实现注释
|
|
61
|
+
|
|
62
|
+
`.cpp` 里的注释只写「为什么」和「这里有什么坑」,不复述语句。
|
|
63
|
+
|
|
64
|
+
```cpp
|
|
65
|
+
// 先判空再取首元素:空订单列表是合法状态,不能假设至少有一单。
|
|
66
|
+
if (orders_.empty()) {
|
|
67
|
+
return OrderResult::kInProgress;
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- 不写「遍历 slots_,检查是否超过阈值」这种把下一行翻译一遍的注释。
|
|
72
|
+
- 注释放在被解释语句的上方;只有短小的状态说明可以放行尾,且一行只挂一个语句。
|
|
73
|
+
- 不要把注释挂在 `if` 和它的 `else` 中间;分支各自的理由写在分支内部上方。
|
|
74
|
+
- 一个注释只解释一件事;解释两件事时拆成两行或两个注释。
|
|
75
|
+
- 相邻的单行注释合成一个注释块,一行一个要点;不要把整段说明挤进一个长行。
|
|
76
|
+
- 处理溢出、越界回退、平台差异时,注释写「为什么这个分支存在」和「不这样会怎样」,不写「这里做了检查」。
|
|
77
|
+
- 不写「这里用了 vector 因为……」「这里改成……更好」这类实现史;设计取舍写进提交说明或文档。
|
|
78
|
+
- 注释掉的代码不保留;需要时从版本历史取。
|
|
79
|
+
|
|
80
|
+
## TODO
|
|
81
|
+
|
|
82
|
+
统一格式:
|
|
83
|
+
|
|
84
|
+
```cpp
|
|
85
|
+
// TODO: 阶段 3 - 支持撤销任务状态更新
|
|
86
|
+
// TODO: issue #42 - 把编号检查抽到 TaskTable
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- `TODO:` 后先写定位(阶段名或 issue 号),再写一个短横线,再写动作。
|
|
90
|
+
- 不写 `TODO(某人)`;人不重要,上下文重要。
|
|
91
|
+
- 不写没有动作的 `TODO: 参考 xxx`;要么写清要做什么,要么删掉。
|
|
92
|
+
- 提交前搜一遍 `TODO`,能做完的做完,做不完的保留定位。
|
|
93
|
+
|
|
94
|
+
## 语言与术语
|
|
95
|
+
|
|
96
|
+
- 注释用中文;标识符、标准库类型、命令、错误消息保持原文。
|
|
97
|
+
- 同一个概念只用一种译法;术语表见 `references/glossary.md`。
|
|
98
|
+
- 不写「显然」「众所周知」;读者不需要被评判。
|
|
99
|
+
- 注释不超过两行连续说明;超过时先问是不是该抽函数或写文档。
|
|
100
|
+
|
|
101
|
+
## 配置注释
|
|
102
|
+
|
|
103
|
+
`.clang-format`、`.clang-tidy`、`.clangd`、CMake 的配置项旁边写「为什么这样设」,不写「这一项是什么」。
|
|
104
|
+
|
|
105
|
+
```yaml
|
|
106
|
+
# 团队统一 100 列;80 列在模板和格式化串里换行过碎。
|
|
107
|
+
ColumnLimit: 100
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
- 配置注释贴在被解释的键上方,不写大段策略头。
|
|
111
|
+
- `.clang-format` 按逻辑组分段,每组一句注释说明这一组在管什么;不要逐键写「设置 X 为 Y」。
|
|
112
|
+
- `.clangd` 每个键组一句话,说明为什么需要这个编译数据库或参数;不要复制整份 CMake 配置。
|
|
113
|
+
- 不在配置里写 `TODO` 或指向某份文档的路径;配置要能独立读懂。
|
|
114
|
+
- `.clang-tidy` 的每个关闭项写一句理由;没有理由的关闭项删掉。
|
|
115
|
+
- 同一个理由只写一次,不逐项复制。
|
|
116
|
+
- 源码注释不写团队政策或流程;那些属于文档,不属于被阅读的代码。
|
|
117
|
+
|
|
118
|
+
## 禁止
|
|
119
|
+
|
|
120
|
+
- 注释里写外部资料路径、项目专属类名或跨仓库引用。
|
|
121
|
+
- 用注释代替类型、`const`、`[[nodiscard]]` 能表达的限制。
|
|
122
|
+
- 大段 ASCII 表格、装饰性图案、表情符号。
|
|
123
|
+
- 把注释当提交说明或变更日志。
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# 声明顺序
|
|
2
|
+
|
|
3
|
+
顺序的作用是让读者先看类型和契约,再看数据。`clang-format` 不管顺序,靠人工和 review 维持。
|
|
4
|
+
|
|
5
|
+
## 类声明
|
|
6
|
+
|
|
7
|
+
```cpp
|
|
8
|
+
class TaskTable {
|
|
9
|
+
public:
|
|
10
|
+
// 1. 类型与别名
|
|
11
|
+
using Tasks = std::array<TaskState, 16>;
|
|
12
|
+
|
|
13
|
+
// 2. 静态常量
|
|
14
|
+
static constexpr int kTaskCount = 16;
|
|
15
|
+
|
|
16
|
+
// 3. 工厂
|
|
17
|
+
static TaskTable CreateEmpty();
|
|
18
|
+
|
|
19
|
+
// 4. 构造、赋值、析构
|
|
20
|
+
TaskTable();
|
|
21
|
+
TaskTable(const TaskTable&) = default;
|
|
22
|
+
TaskTable& operator=(const TaskTable&) = default;
|
|
23
|
+
~TaskTable();
|
|
24
|
+
|
|
25
|
+
// 5. 其他成员函数
|
|
26
|
+
[[nodiscard]] bool Contains(int id) const;
|
|
27
|
+
[[nodiscard]] TaskState GetTaskState(int id) const;
|
|
28
|
+
void SetTaskState(int id, TaskState state);
|
|
29
|
+
|
|
30
|
+
// 6. 数据成员
|
|
31
|
+
private:
|
|
32
|
+
Tasks tasks_{};
|
|
33
|
+
bool is_open_ = false;
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- `struct` 的数据成员放最前;有 invariant 时改用 `class`。
|
|
38
|
+
- `public:` 在前,`private:` 在后;`private:` 前空一行,数据成员之间不空行。
|
|
39
|
+
- 静态常量用 `static constexpr`;跨文件共享的常量放命名空间并 `inline constexpr`。
|
|
40
|
+
- 工厂放在构造函数之前;返回 `TaskTable` 的工厂用 `Create` 开头,名字说明创建的是什么状态。
|
|
41
|
+
- 用户声明的特殊成员函数集中放第 4 组;`= default` 也写出来,读者才知道是故意默认。
|
|
42
|
+
- 不要把数据成员插在函数之间;数据成员永远在最后一组。
|
|
43
|
+
|
|
44
|
+
## 命名空间作用域的头文件
|
|
45
|
+
|
|
46
|
+
```cpp
|
|
47
|
+
#pragma once
|
|
48
|
+
|
|
49
|
+
#include <array>
|
|
50
|
+
#include <cstdint>
|
|
51
|
+
#include <string>
|
|
52
|
+
|
|
53
|
+
namespace taskflow {
|
|
54
|
+
|
|
55
|
+
inline constexpr int kTaskCount = 16;
|
|
56
|
+
|
|
57
|
+
enum class TaskState : std::uint8_t { kPending, kActive, kDone };
|
|
58
|
+
|
|
59
|
+
struct TaskRecord {
|
|
60
|
+
int id = 0;
|
|
61
|
+
TaskState state = TaskState::kPending;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
[[nodiscard]] bool Contains(int id);
|
|
65
|
+
[[nodiscard]] std::string TaskStateToString(TaskState state);
|
|
66
|
+
|
|
67
|
+
class TaskTable {
|
|
68
|
+
public:
|
|
69
|
+
// 类内仍按上面的六组顺序
|
|
70
|
+
private:
|
|
71
|
+
std::array<TaskState, kTaskCount> tasks_{};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
} // namespace taskflow
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
顺序:包含 → 命名空间 → `inline constexpr` 常量 → `enum class` → `struct`(被依赖的在前)→ 自由函数 → 类。类的公共接口类型放在类内最前。
|
|
78
|
+
|
|
79
|
+
## `.cpp` 的定义顺序
|
|
80
|
+
|
|
81
|
+
- 与头文件声明同序:先类型和常量,再自由函数,再成员函数;成员函数按类内声明顺序。
|
|
82
|
+
- 文件头注释之后先放本文件的 `namespace taskflow {`,再放公共定义;不要在生产 `.cpp` 里开匿名命名空间,文件内辅助函数用 `static` 并放在项目命名空间内,测试文件例外。
|
|
83
|
+
- 构造函数、析构函数、拷贝/移动定义放在成员函数组的最前;`= default` 的定义放 `.cpp` 时紧跟声明顺序。
|
|
84
|
+
- 不要为了「先编译」重排定义;C++ 不要求定义顺序,读者顺序优先。
|
|
85
|
+
|
|
86
|
+
## 空行
|
|
87
|
+
|
|
88
|
+
| 位置 | 空行 |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| 类内六个组之间 | 1 行 |
|
|
91
|
+
| 同组函数之间 | 1 行 |
|
|
92
|
+
| 数据成员之间 | 0 行 |
|
|
93
|
+
| `private:` 之前 | 1 行 |
|
|
94
|
+
| 命名空间内顶层实体之间 | 1 行 |
|
|
95
|
+
| 函数体内逻辑组之间 | 1 行,最多 1 行 |
|
|
96
|
+
|
|
97
|
+
连续两个空行视为格式缺陷;`clang-format` 的 `MaxEmptyLinesToKeep: 1` 会压掉,但不要依赖它。
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# 异常与错误
|
|
2
|
+
|
|
3
|
+
本技能对 Google「不使用异常」做书面偏离:**异常可用,但只用于合同违反和程序边界**。可预期的失败用返回值表达。这样既保留 C++ 的清晰错误路径,又不让异常成为控制流。
|
|
4
|
+
|
|
5
|
+
## 抛还是返回
|
|
6
|
+
|
|
7
|
+
| 情形 | 做法 | 例子 |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| 用户输入非法、文件不存在、解析失败 | 返回结果类型 / `bool` / `std::optional` | `TryParse()` → `ParseResult` |
|
|
10
|
+
| 查询越界、构造参数不满足 invariant | 抛合同异常 | `GetTaskState(TaskId)` 越界抛 `std::out_of_range` |
|
|
11
|
+
| 程序入口、CLI、工具边界 | 捕获 `std::exception`,打印并返回非零 | `main()` 捕获后 `std::format("错误: {}", e.what())` |
|
|
12
|
+
| 内存不足、系统调用失败 | 让标准异常传播,或转成结果类型;不要吞掉 | `std::bad_alloc` 不捕获 |
|
|
13
|
+
| 析构函数 | 不抛;失败只记录或忽略 | `~File()` 不抛 |
|
|
14
|
+
| 可恢复的业务失败 | 不抛;用结果类型让调用方分支 | `ReserveResult::kOccupied` |
|
|
15
|
+
|
|
16
|
+
判断句:**调用方能否在不看实现的情况下合理避免?** 能避免的是合同违反(抛);不能避免的是可预期失败(返回)。
|
|
17
|
+
|
|
18
|
+
## 合同异常
|
|
19
|
+
|
|
20
|
+
- 在声明注释里写清:什么条件抛、抛什么类型、调用方应如何避免。
|
|
21
|
+
- 类型选择:参数非法 `std::invalid_argument`;越界 `std::out_of_range`;状态不允许 `std::logic_error`;资源失败用标准库已有类型。
|
|
22
|
+
- 异常消息用 `std::format` 组合,包含操作、关键值和期望范围:
|
|
23
|
+
|
|
24
|
+
```cpp
|
|
25
|
+
throw std::out_of_range{
|
|
26
|
+
std::format("GetTaskState: id={} 超出 [0, {})", id, kTaskCount)};
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- 不要抛裸 `std::string`、`const char*` 或自定义无继承的类型;从 `std::exception` 派生时实现 `what()`。
|
|
30
|
+
- 不要用异常做正常分支;循环里用异常控制流程视为缺陷。
|
|
31
|
+
|
|
32
|
+
## 捕获
|
|
33
|
+
|
|
34
|
+
- 按 `const` 引用捕获:`catch (const std::exception& error)`。
|
|
35
|
+
- 只捕获能处理的类型;不认识的异常继续抛,不要 `catch (...)` 后静默返回。
|
|
36
|
+
- 捕获后要么恢复成有效状态,要么返回错误;不要带着半初始化对象继续运行。
|
|
37
|
+
- `noexcept` 只写在确实不抛的函数上;析构函数、移动操作默认不抛,除非有明确理由。
|
|
38
|
+
|
|
39
|
+
## 程序边界
|
|
40
|
+
|
|
41
|
+
`main()` 是异常边界:库让合同异常向上传播,入口统一翻译成用户可读的消息和非零退出码。
|
|
42
|
+
|
|
43
|
+
```cpp
|
|
44
|
+
int main() {
|
|
45
|
+
try {
|
|
46
|
+
return Run(std::cin, std::cout);
|
|
47
|
+
} catch (const std::exception& error) {
|
|
48
|
+
std::cerr << std::format("错误: {}\n", error.what());
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- 消息面向用户,不打印栈、不暴露内部路径;调试信息写日志或测试输出。
|
|
55
|
+
- 退出码:成功 `0`;可预期失败 `1`;用法错误可以用 `2`。同一个程序保持一致。
|
|
56
|
+
- 不要在每个函数里 `try/catch` 后打印;让异常走到边界,中间层只在能补偿时捕获。
|
|
57
|
+
|
|
58
|
+
## 消息文案与日志
|
|
59
|
+
|
|
60
|
+
异常消息是给调用者和用户看的诊断,不是日志流:
|
|
61
|
+
|
|
62
|
+
- 格式固定为 `<ApiName>: <主体> <原因>`,例如 `GetTaskState: id=3 超出 [0, 3)`。
|
|
63
|
+
- 写操作名和关键值,不写「出错了」「非法」这类没有信息量的词。
|
|
64
|
+
- 不引入日志框架,不在库代码里打印;需要诊断上下文时由边界层记录,异常本身只携带消息。
|
|
65
|
+
- 同一条消息在测试里断言稳定的前缀,不断言完整路径或平台相关文本。
|
|
66
|
+
|
|
67
|
+
## 与 `std::format` 的关系
|
|
68
|
+
|
|
69
|
+
- 异常消息、用户提示、测试断言消息优先 `std::format`。
|
|
70
|
+
- C++20 没有 `std::print`;格式化后写入流。
|
|
71
|
+
- 格式串里的类型要匹配;不要把用户输入直接拼进格式串(格式串必须是可信字面量)。
|
|
72
|
+
|
|
73
|
+
## 检查清单
|
|
74
|
+
|
|
75
|
+
- [ ] 每个抛异常的函数在声明注释里写了条件和类型。
|
|
76
|
+
- [ ] 可预期失败没有用异常。
|
|
77
|
+
- [ ] 捕获按 `const` 引用,未吞掉未知异常。
|
|
78
|
+
- [ ] 析构函数不抛。
|
|
79
|
+
- [ ] 入口有统一捕获,消息用 `std::format`,退出码一致。
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# 术语
|
|
2
|
+
|
|
3
|
+
代码标识符用英文;注释和说明用中文时,同一个概念只用一种译法。下表是本项目的默认译法,项目技能可以增补,但不要改已有词的含义。
|
|
4
|
+
|
|
5
|
+
| 英文 | 中文 | 说明 |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| declaration | 声明 | 告诉编译器名字和类型,不分配存储 |
|
|
8
|
+
| definition | 定义 | 分配存储或给出函数体 |
|
|
9
|
+
| translation unit | 翻译单元 | 一个 `.cpp` 加上它包含的头,预处理后的整体 |
|
|
10
|
+
| header | 头文件 | `.h` / `.hpp`,放声明和可内联实现 |
|
|
11
|
+
| source file | 源文件 | `.cpp`,放普通函数体 |
|
|
12
|
+
| target | 目标 | CMake 的构建单元,如 `app`、`greeting` |
|
|
13
|
+
| observable behavior | 可观察行为 | 用户或测试能看到的输出、返回值、退出码、文件内容 |
|
|
14
|
+
| boundary | 边界 | 库与程序、模块与模块之间的职责分界 |
|
|
15
|
+
| invariant | 不变量 | 对象在任何公共操作后都必须成立的条件 |
|
|
16
|
+
| contract | 契约 | 调用方必须满足的前置条件,以及被调用方承诺的结果 |
|
|
17
|
+
| precondition | 前置条件 | 调用前必须为真;违反通常抛合同异常 |
|
|
18
|
+
| postcondition | 后置条件 | 调用成功后保证为真 |
|
|
19
|
+
| exception safety | 异常安全 | 抛出后对象仍有效、资源不泄漏的保证 |
|
|
20
|
+
| ownership | 所有权 | 谁负责释放资源;`unique_ptr` 表达独占 |
|
|
21
|
+
| value semantics | 值语义 | 拷贝后互不影响 |
|
|
22
|
+
| reference semantics | 引用语义 | 多个名字共享同一对象 |
|
|
23
|
+
| aggregate | 聚合 | 无用户构造函数、无私有成员,可直接列表初始化 |
|
|
24
|
+
| RAII | 资源获取即初始化 | 构造函数获取资源,析构函数释放 |
|
|
25
|
+
| compile database | 编译数据库 | `compile_commands.json`,clangd/clang-tidy 用它还原编译命令 |
|
|
26
|
+
| static analysis | 静态分析 | 不运行程序就检查代码,如 clang-tidy |
|
|
27
|
+
| formatting | 格式化 | 按固定规则排版,如 clang-format;也指 `std::format` 的消息组合 |
|
|
28
|
+
| diagnostic | 诊断 | 编译器或工具给出的一条问题报告 |
|
|
29
|
+
| warning-as-error | 警告即错误 | 把警告当失败处理,避免积累 |
|
|
30
|
+
| undefined behavior | 未定义行为 | 标准不规定结果,必须避免 |
|
|
31
|
+
| half-open range | 半开区间 | `[begin, end)`,包含 begin 不包含 end |
|
|
32
|
+
| Yoda condition | 尤达条件 | 常量写在左侧的比较,如 `0 == x`,本项目禁止 |
|
|
33
|
+
| out-parameter | 输出参数 | 通过引用/指针返回结果;优先用返回值替代 |
|
|
34
|
+
| access control | 访问控制 | `public` / `protected` / `private` 决定谁能使用成员 |
|
|
35
|
+
| data member | 数据成员 | 属于对象的数据;不要叫「成员变量」 |
|
|
36
|
+
| member function | 成员函数 | 声明在类内、可访问对象的函数 |
|
|
37
|
+
| object | 对象 | 一块具有类型和生命周期的存储 |
|
|
38
|
+
| lifetime | 生命周期 | 对象从构造完成到析构开始之间 |
|
|
39
|
+
| value category | 值类别 | 左值、右值等表达式分类,决定能否绑定引用和移动 |
|
|
40
|
+
| dangling reference | 悬垂引用 | 引用的对象已经销毁 |
|
|
41
|
+
| use-after-free | 释放后使用 | 释放后仍访问对象,未定义行为 |
|
|
42
|
+
| memory leak | 内存泄漏 | 失去所有权但未释放的资源 |
|
|
43
|
+
| double free | 重复释放 | 同一资源释放两次,未定义行为 |
|
|
44
|
+
| storage duration | 存储期 | 自动、静态、线程、动态四种生存范围 |
|
|
45
|
+
| scope | 作用域 | 名字可见的代码区域 |
|
|
46
|
+
| qualified name | 限定名 | 带命名空间或类前缀的名字,如 `inventory::Item` |
|
|
47
|
+
| namespace | 命名空间 | 给名字分组、避免冲突的作用域 |
|
|
48
|
+
| forward declaration | 前向声明 | 只声明名字不给出定义,减少头文件依赖 |
|
|
49
|
+
| include guard | 包含守卫 | 防止头文件重复展开;本项目用 `#pragma once` |
|
|
50
|
+
| ODR | 单一定义规则 | 一个实体在整个程序中至多一个定义,内联实体例外 |
|
|
51
|
+
| overload | 重载 | 同名但参数不同的函数 |
|
|
52
|
+
| template | 模板 | 按类型或值参数生成代码的机制 |
|
|
53
|
+
| instantiation | 实例化 | 用具体参数生成模板实体 |
|
|
54
|
+
| concept | 概念 | C++20 对模板参数的命名约束 |
|
|
55
|
+
| type deduction | 类型推导 | 由初始化器或实参推断类型,如 `auto` |
|
|
56
|
+
| narrowing | 窄化转换 | 可能丢信息的隐式转换,列表初始化禁止 |
|
|
57
|
+
| value initialization | 值初始化 | `T{}` 产生零值或默认构造的对象 |
|
|
58
|
+
| zero initialization | 零初始化 | 静态存储对象先置零 |
|
|
59
|
+
| default member initializer | 默认成员初始化器 | 类内 `member_{}` 或 `= value` |
|
|
60
|
+
| copy elision | 拷贝消除 | 编译器直接构造目标对象,省略拷贝/移动 |
|
|
61
|
+
| RVO | 返回值优化 | 拷贝消除的一种,返回局部对象时直接构造 |
|
|
62
|
+
| move semantics | 移动语义 | 把资源所有权从源对象转出 |
|
|
63
|
+
| moved-from state | 移后状态 | 对象仍有效但值未指定,只能析构或重置 |
|
|
64
|
+
| `noexcept` | 不抛异常保证 | 函数承诺不向外抛异常 |
|
|
65
|
+
| iterator | 迭代器 | 指向容器元素的泛化指针 |
|
|
66
|
+
| invalidation | 失效 | 容器修改后旧迭代器/引用不可再用 |
|
|
67
|
+
| allocator | 分配器 | 容器用来申请释放内存的策略对象 |
|
|
68
|
+
| `std::string_view` | 字符串视图 | 不拥有字符的只读视图,生命周期由调用方保证 |
|
|
69
|
+
| `std::span` | 连续序列视图 | C++20 不拥有元素的连续区间视图 |
|
|
70
|
+
| executable | 可执行文件 | 带入口、可被操作系统加载运行的文件 |
|
|
71
|
+
| static library | 静态库 | `.a` / `.lib`,链接时复制所需目标文件 |
|
|
72
|
+
| shared library | 共享库 | `.so` / `.dylib` / `.dll`,运行时加载 |
|
|
73
|
+
| link | 链接 | 把目标文件和库合成可执行文件或共享库 |
|
|
74
|
+
| load | 加载 | 运行时把可执行文件和依赖库映射进进程 |
|
|
75
|
+
| RPATH | 运行时库搜索路径 | 可执行文件内记录的共享库查找目录 |
|
|
76
|
+
| undefined reference | 未定义引用 | 链接器找不到某个符号的定义 |
|
|
77
|
+
| compile command | 编译命令 | 单个翻译单元的完整命令行 |
|
|
78
|
+
| build directory | 构建目录 | 存放生成物和缓存的目录,不进版本库 |
|
|
79
|
+
| build tree | 构建树 | 一次配置产生的完整目录结构 |
|
|
80
|
+
| reproducible build | 可复现构建 | 相同输入产生相同产物 |
|
|
81
|
+
|
|
82
|
+
## 容易混的词
|
|
83
|
+
|
|
84
|
+
- **声明 vs 定义**:头文件里多是声明;函数体、全局变量初始化是定义。
|
|
85
|
+
- **参数 vs 实参**:声明里叫参数(parameter),调用点叫实参(argument);中文口语都叫参数时,写文档要区分。
|
|
86
|
+
- **接口 vs 实现**:公共头是接口,`.cpp` 是实现;不要用「接口」指整个类。
|
|
87
|
+
- **异常 vs 错误**:异常是语言机制;错误是结果。可预期错误用返回值,不叫「异常」。
|
|
88
|
+
- **格式化 vs 排版**:`std::format` 组合消息;clang-format 排版源码。不要混用「格式化」指代两者。
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Google C++ 风格:本技能采用的版本
|
|
2
|
+
|
|
3
|
+
以 [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) 为基线,按 C++20 工程的需要做了少量书面偏离。偏离必须写在这里,不能只存在于某个人的习惯里。
|
|
4
|
+
|
|
5
|
+
## 语言版本
|
|
6
|
+
|
|
7
|
+
- 目标标准是 C++20。不使用 C++23 的 `std::print`、`std::expected`、`std::stacktrace` 等设施。
|
|
8
|
+
- 不使用编译器扩展;需要扩展时先停下来,改成标准写法。
|
|
9
|
+
- 一个工程只用一个编译器配置;不要在同一棵源码树上混用 Clang 与 GCC 的扩展。
|
|
10
|
+
|
|
11
|
+
## 头文件
|
|
12
|
+
|
|
13
|
+
- 每个头文件用 `#pragma once`。这是对 Google「使用 `#define` 守卫」的偏离;项目统一用前者,避免守卫名漂移。
|
|
14
|
+
- 头文件必须自包含:自己用到的类型、宏、标准库头都直接包含,不依赖调用方的包含顺序。
|
|
15
|
+
- `.cpp` 第一个包含自己的配对头,然后直接包含它用到的每个标准库头;不要依赖公共头替它转包含。
|
|
16
|
+
- 包含顺序:相关头文件 → C 系统头 → C++ 标准库 → 其他库 → 本项目。每组之间空一行,组内按字母序;`clang-format` 的 `IncludeCategories` 负责维持。
|
|
17
|
+
- 本项目头文件用引号加裸文件名:`#include "inventory.h"`,路径相对于公共 include 根。不要写 `#include "component/inventory.h"`,也不要用尖括号包含项目头。
|
|
18
|
+
- 公共头文件直接放在组件的 `include/` 根下;不要为了「整齐」再加一层命名空间目录。
|
|
19
|
+
- 跨文件共享的常量用 `inline constexpr`,放在命名空间里并带 `k` 前缀;头文件里不要定义非 inline 的普通全局变量。
|
|
20
|
+
- 头文件里只放模板、`constexpr`、类内联小函数和声明;普通函数的函数体放 `.cpp`。
|
|
21
|
+
|
|
22
|
+
## 命名
|
|
23
|
+
|
|
24
|
+
| 种类 | 规则 | 例子 |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| 类型、类、结构、枚举、别名 | PascalCase | `TaskTable`, `OrderResult`, `TaskState` |
|
|
27
|
+
| 函数、成员函数 | PascalCase | `Contains`, `GetCurrentOrder` |
|
|
28
|
+
| 变量、参数、局部变量 | snake_case | `task_count`, `current_order` |
|
|
29
|
+
| 类数据成员 | snake_case + 尾随 `_` | `tasks_`, `orders_` |
|
|
30
|
+
| 常量、枚举子 | `k` + PascalCase | `kTaskCount`, `Order::kDraft` |
|
|
31
|
+
| 命名空间 | snake_case | `taskflow`, `warehouse` |
|
|
32
|
+
| 宏 | 尽量不用;必须用时全大写并带项目前缀 | `CPP_WORKFLOW_ASSERT` |
|
|
33
|
+
|
|
34
|
+
- 一个 API 不混用两套命名。对外函数用 PascalCase,成员变量用 snake_case,不要出现 `get_task` 与 `GetTask` 并存。
|
|
35
|
+
- 查询用 `Get` + PascalCase:`GetCurrentOrder()`、`GetHistory()`;不要用成员名直接当访问器(`current_order()`)。
|
|
36
|
+
- 谓词用 `Is` / `Has`:`Contains()`、`HasStock()`。返回 `bool` 但语义不是判断的函数,换成领域动词。
|
|
37
|
+
- 尝试用 `Try`:`TryReserve()` 返回结果类型;确定性动作直接用领域动词:`CompleteTask()`、`Reset()`。
|
|
38
|
+
- 映射函数的名字要含来源与目标角色:`OrderToReceipt()`、`TaskTableToString()`;禁止裸 `To()` / `Of()`。
|
|
39
|
+
- 计算函数用动词开头:`ScoreOrder()`、`CollectPendingTasks()`。
|
|
40
|
+
- `current_` 前缀只在有多个候选、且「当前」确实可能混淆时使用;只有一个当前对象时不要加。
|
|
41
|
+
- 避免 `Manager`、`Helper`、`Util` 这类不说明职责的后缀;名字要能回答「它负责什么」。
|
|
42
|
+
- 测试名用 PascalCase 描述行为,例如 `RejectsOutOfRangeTask`;测试文件用 snake_case。
|
|
43
|
+
|
|
44
|
+
## 类型与声明
|
|
45
|
+
|
|
46
|
+
- 纯数据聚合用 `struct`;有不变量的类型用 `class`,不变量在构造函数和公共成员函数里维持。
|
|
47
|
+
- 单参数构造函数写 `explicit`;零参数构造函数不写。
|
|
48
|
+
- 默认成员初始化写在声明处(NSDMI),不要在构造函数初始化列表里重复。容器和有默认构造的子对象不写 `{}`。
|
|
49
|
+
- 默认用花括号初始化:`int count{};`、`std::vector<int> values{1, 2, 3};`。只有需要对比、`auto` 推导或赋值语义时用 `=`;不要用 `()` 初始化普通对象。
|
|
50
|
+
- 类声明顺序:类型与别名 → 静态常量 → 工厂 → 构造/赋值/析构 → 其他函数 → 数据成员。
|
|
51
|
+
- 用户声明的拷贝/移动/析构函数在头文件声明、在 `.cpp` 定义;只有零参数且默认实现足够时才写 `= default`。
|
|
52
|
+
- 查询成员函数标尾部 `const`;返回结果对象的 API 加 `[[nodiscard]]`,不要给所有非 `void` 函数机械添加。
|
|
53
|
+
- 所有权优先 `std::unique_ptr`;没有共享所有权的理由时不用 `std::shared_ptr`。禁止裸 `new` / `delete`。
|
|
54
|
+
- 禁止 C 风格强制转换;用 `static_cast`、`const_cast`、`reinterpret_cast`、`std::bit_cast`,并让 `clang-tidy` 的 `google-readability-casting` 兜底。
|
|
55
|
+
- 生产代码不用匿名命名空间隐藏实现;私有实现放 `.cpp` 的 `static` 函数或独立组件,测试通过公共头使用。
|
|
56
|
+
|
|
57
|
+
## 函数
|
|
58
|
+
|
|
59
|
+
- 优先返回值,不用输出参数:返回 `std::string`、`std::vector<T>`、结果结构体。
|
|
60
|
+
- 已有成员按 `const` 引用返回,例如 `GetHistory()`;不要返回内部容器的可变引用。
|
|
61
|
+
- 函数保持短小;超过约 40 行时先拆出有名字的步骤,而不是加注释分节。
|
|
62
|
+
- 虚函数不用默认参数;默认参数只用于非虚的便利接口。
|
|
63
|
+
- 除 lambda 和必须推导的模板外,不写尾置返回类型。
|
|
64
|
+
- 只对自己的类型重载运算符;不重载 `&&`、`||`、`,`、一元 `&`;不定义用户字面量。
|
|
65
|
+
|
|
66
|
+
## 表达式与整数
|
|
67
|
+
|
|
68
|
+
- 比较不写 Yoda 形式:`x == 0`、`task == TaskState::kPending`;不要写 `0 == x`、`nullptr == p`。
|
|
69
|
+
- 半开区间写成两个「变量在左」的比较:`id >= 0 && id < kTaskCount`;不要写 `0 <= id`。
|
|
70
|
+
- 混合优先级算术加显式括号,消除 `readability-math-missing-parentheses`。
|
|
71
|
+
- 业务编号、尺寸用 `std::int32_t`;容器长度和索引用 `std::size_t`。越界由边界检查函数统一判断,负值和上界都算非法。
|
|
72
|
+
- 封闭状态码可以用 `enum class Name : std::uint8_t`,只为缩小存储;普通枚举用默认底层类型。枚举值总是带作用域:`OrderResult::kSuccess`。
|
|
73
|
+
- 枚举的尾随注释要么全写要么全不写;语义结果写在最前,`///<` 对齐。
|
|
74
|
+
- 浮点不用 `==` 比较计算结果;按量纲给容差,金额用最小货币单位的整数。
|
|
75
|
+
- 数值宽度、符号、枚举、强类型、`const` 成员和下标访问的完整规则见 `references/types.md`;初始化、RAII、移动和模板见 `references/language.md`。
|
|
76
|
+
|
|
77
|
+
## 异常
|
|
78
|
+
|
|
79
|
+
Google 原文说「我们不使用 C++ 异常」。本技能按使用方要求做书面偏离:**可以在合适的地方使用异常**,但只用于合同违反和程序边界,不用异常表达可预期的业务失败。完整规则见 `references/exceptions.md`。
|
|
80
|
+
|
|
81
|
+
## 输出与格式化
|
|
82
|
+
|
|
83
|
+
- 组合消息以 `std::format` 为主:`std::format("任务 {} 进入状态 {}", task, state)`。
|
|
84
|
+
- C++20 没有 `std::print`;格式化结果写到 `std::cout` / `std::cerr` / `std::ostringstream`。
|
|
85
|
+
- 简单字面量输出可以直接用 `std::cout << "..."`;需要拼接变量、对齐、数值格式时用 `std::format`。
|
|
86
|
+
- 列宽上限 100(`ColumnLimit: 100`),由 `clang-format` 执行;不要手工调换行。
|
|
87
|
+
- 浮点输出固定精度,整数不依赖隐式格式;`std::format` 的格式串写全。
|
|
88
|
+
|
|
89
|
+
## 注释
|
|
90
|
+
|
|
91
|
+
- 注释写意图、契约、取舍,不复述代码。
|
|
92
|
+
- 文件头、章节横幅、声明注释、实现注释、配置注释的格式见 `references/comments.md`。
|
|
93
|
+
- TODO 统一写 `// TODO: <阶段或 issue> - <要做什么>`;不要写 `@todo`、`FIXME` 或只有「参考」没有动作的占位。
|
|
94
|
+
|
|
95
|
+
## 对 Google 的书面偏离
|
|
96
|
+
|
|
97
|
+
| 主题 | Google | 本项目 | 理由 |
|
|
98
|
+
| --- | --- | --- | --- |
|
|
99
|
+
| 头文件守卫 | `#define` 守卫 | `#pragma once` | 统一、无守卫名漂移;主流编译器都支持 |
|
|
100
|
+
| 异常 | 不使用 | 合同违反/程序边界可用 | 用户明确要求「能使用异常的地方使用合理的异常」 |
|
|
101
|
+
| 访问器 | 可用 snake_case | `Get` + PascalCase | 与函数命名统一,调用点可搜索 |
|
|
102
|
+
| 列宽 | 80 | 100 | 现代屏幕与 C++ 模板可读性;团队统一 |
|
|
103
|
+
| 项目头包含 | 相对源根 | 公共 include 根 + 裸文件名 | 公共头直接放 `include/`,减少路径噪音 |
|
|
104
|
+
| 源文件扩展名 | `.cc` | `.h` / `.cpp` | 现有工程、编辑器和材料统一用 `.cpp` |
|
|
105
|
+
| 风格检查器 | `cpplint.py` | `clang-format` + `clang-tidy` | 与编译数据库集成,可自动修复;不再维护 Python 检查器 |
|
|
106
|
+
| 枚举底层类型 | 默认 | 封闭状态码可 `: std::uint8_t` | 缩小存储,边界明确 |
|
|
107
|
+
| 整数宽度 | `int` / 实现定义宽度 | 业务编号 `std::int32_t`,容器下标 `std::size_t`,状态码 `std::uint8_t` | 协议、布局和负编号需要可预测宽度 |
|
|
108
|
+
|
|
109
|
+
偏离之外仍以 Google 原文为准;新增偏离必须先写进本表,再改配置或代码。
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# 语言基础与对象语义
|
|
2
|
+
|
|
3
|
+
这里只写“换一台机器、换一个编译器仍然成立”的 C++20 语言规则。项目专属类型、协议字段和构建目录不在本文件。
|
|
4
|
+
|
|
5
|
+
## 值与对象
|
|
6
|
+
|
|
7
|
+
- 类型决定允许的操作和表示;对象是某类型的一块存储;值是可以复制、比较、传递的内容。
|
|
8
|
+
- 变量有名字、类型、存储期和作用域。局部变量在离开作用域时销毁;`static` 局部变量在首次执行到声明时初始化,程序结束时销毁。
|
|
9
|
+
- 声明让名字可见,定义分配存储或给出函数体。一个实体可以声明多次,但只能定义一次;头文件只放声明,定义放 `.cpp`。
|
|
10
|
+
- 引用是已有对象的别名,必须初始化,不能重新绑定;指针可以为空,也可以改指向。接口优先用引用表达“必须存在”,用指针表达“可以不存在”。
|
|
11
|
+
|
|
12
|
+
## 初始化与列表初始化
|
|
13
|
+
|
|
14
|
+
默认用花括号初始化,只在需要对比、`auto` 推导或赋值语义时用 `=`:
|
|
15
|
+
|
|
16
|
+
```cpp
|
|
17
|
+
int count{}; // 值初始化,0
|
|
18
|
+
std::vector<int> values{1, 2, 3}; // 列表初始化
|
|
19
|
+
std::string name = "demo"; // 拷贝初始化,类型明确
|
|
20
|
+
auto total = 0.0; // auto 只能用 =,不能用 {}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- `T value{}` 做值初始化:内置类型得 0,类类型调用默认构造函数。
|
|
24
|
+
- 花括号禁止窄化转换:`int x{3.5}` 编译失败,`int x = 3.5` 静默截断。需要截断时显式 `static_cast<int>(3.5)`。
|
|
25
|
+
- `std::vector<int> values(3)` 是三个 0;`values{3}` 是一个元素 3。两种括号语义不同,不要凭习惯混用。
|
|
26
|
+
- 类成员在声明处写默认成员初始化(NSDMI):标量 `count_{0}`,数组 `items_{}`,容器和类类型成员不写空 `{}`。
|
|
27
|
+
- 构造函数初始化列表按成员声明顺序执行,不按书写顺序;初始化列表顺序与声明顺序保持一致,避免依赖未初始化成员。
|
|
28
|
+
|
|
29
|
+
## `auto` 与类型推导
|
|
30
|
+
|
|
31
|
+
- `auto` 适合“右侧类型显然且无关紧要”的局部变量,例如迭代器、`make_unique` 结果、范围 `for`。
|
|
32
|
+
- 公共接口、类成员、函数返回类型写显式类型;返回值需要隐藏实现细节时才可以写 `auto`,且头文件里要能看出契约。
|
|
33
|
+
- `auto` 会丢掉顶层 `const` 和引用:`auto value = const_ref` 得到副本;需要引用时写 `const auto&`。
|
|
34
|
+
- 结构化绑定 `auto [key, value] : map` 中,`key` 是副本;要避免复制时写 `const auto& [key, value]`。
|
|
35
|
+
|
|
36
|
+
## `const`、`constexpr`、`consteval`、`constinit`
|
|
37
|
+
|
|
38
|
+
| 关键字 | 承诺 | 典型位置 |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `const` | 初始化后不能通过这个名字修改 | 局部变量、参数、成员函数尾部 |
|
|
41
|
+
| `constexpr` | 能在编译期求值(满足条件时) | 常量、函数、构造函数 |
|
|
42
|
+
| `consteval` | 必须在编译期求值 | 编译期校验函数 |
|
|
43
|
+
| `constinit` | 静态存储期变量在编译期完成初始化 | 全局/静态变量 |
|
|
44
|
+
| `inline constexpr` | 头文件里可共享的编译期常量,避免 ODR 问题 | 命名空间作用域常量 |
|
|
45
|
+
|
|
46
|
+
- 共享常量写在头文件的命名空间作用域:`inline constexpr int kMaxRows = 3;`,不要用 `#define`。
|
|
47
|
+
- `constexpr` 函数的定义要放在头文件里,否则其他翻译单元无法在编译期调用。
|
|
48
|
+
- 不要给所有函数加 `constexpr`;只有确实需要在编译期使用、且实现满足约束时才加。
|
|
49
|
+
- `constinit` 只保证初始化时机,不保证不可修改;需要不可修改时同时写 `const`。
|
|
50
|
+
- C++20 起 `constexpr` 函数可以包含循环、局部变量和部分动态分配,但不要借此把运行期逻辑塞进编译期。
|
|
51
|
+
|
|
52
|
+
## 表达式与比较
|
|
53
|
+
|
|
54
|
+
- 比较写成“变量在左、常量在右”:`if (id < kTaskCount)`,不写 Yoda 条件 `if (kTaskCount > id)`。编译器会在漏写 `=` 时给出警告,可读性也更好。
|
|
55
|
+
- 混合优先级的算术表达式加括号:`(a + b) * c`,不依赖读者记忆优先级。`readability-math-missing-parentheses` 会检查。
|
|
56
|
+
- 区间用半开区间 `[low, high)`;判空用 `empty()`,不要用 `size() == 0`。
|
|
57
|
+
- 逻辑表达式按语义分组,一组一个括号;不要把多个否定串成一行。
|
|
58
|
+
- 自增自减单独成句;不要在同一个表达式里对同一对象多次修改,那是未定义行为。
|
|
59
|
+
|
|
60
|
+
## 函数、参数与返回值
|
|
61
|
+
|
|
62
|
+
- 函数名用 PascalCase,动词开头;查询用 `Is` / `Has` / `Can` / `Get` / `Find`,转换用 `SourceToTarget`。
|
|
63
|
+
- 输入参数默认按值传小对象、按 `const&` 传大对象;需要修改调用方对象时用引用参数,并在名字或注释里标 `[out]`。
|
|
64
|
+
- 优先返回值而不是输出参数;需要返回多个值时用 `struct` 聚合,不用多个 `bool` 输出参数。
|
|
65
|
+
- 返回容器或大对象时按值返回,依赖 RVO / 移动;不要返回局部对象的引用或指针。
|
|
66
|
+
- 单参数构造函数加 `explicit`,除非确实需要隐式转换;转换运算符也加 `explicit`。
|
|
67
|
+
- 不在虚函数上写默认参数;默认参数在静态类型处绑定,派生类改默认值会造成意外。
|
|
68
|
+
- 函数只做一件事;超过一屏时先找可命名的子步骤,不要用注释给长函数分段。
|
|
69
|
+
|
|
70
|
+
## 类、结构体与特殊成员
|
|
71
|
+
|
|
72
|
+
- `struct` 用于公开数据的聚合;`class` 用于维护不变量、隐藏表示。两者只差默认访问权限,按语义选。
|
|
73
|
+
- 数据成员默认私有;需要暴露只读视图时提供 `const` 成员函数,不返回可变内部引用。
|
|
74
|
+
- 单参数构造函数用 `explicit`;构造函数失败时抛异常,不留下半初始化对象。
|
|
75
|
+
- 特殊成员(构造、析构、拷贝、移动)在头文件声明,在 `.cpp` 定义;只有默认实现足够且零参数时才写 `= default`。
|
|
76
|
+
- 定义了析构函数、拷贝构造或拷贝赋值之一时,认真考虑其余特殊成员;用 `= delete` 明确禁止不需要的操作。
|
|
77
|
+
- 多态基类析构函数必须是 `virtual` 或 `protected`;否则通过基类指针删除派生对象是未定义行为。
|
|
78
|
+
- 不在构造函数里调用虚函数;此时派生部分还没构造完。
|
|
79
|
+
|
|
80
|
+
## RAII、所有权与移动
|
|
81
|
+
|
|
82
|
+
- 资源获取即初始化:用对象生存期管理内存、文件、锁、套接字;析构函数负责释放。
|
|
83
|
+
- 默认用 `std::unique_ptr<T>` 表达独占所有权;需要共享所有权时才用 `std::shared_ptr<T>`,并用 `std::weak_ptr` 打破循环。
|
|
84
|
+
- 不写裸 `new` / `delete`;需要动态数组时用 `std::vector`,需要单个对象时用 `std::make_unique`。
|
|
85
|
+
- 移动是“把资源指针转走并置空源对象”的转换;`std::move` 只是 `static_cast<T&&>`,本身不移动任何东西。
|
|
86
|
+
- 移动后的对象处于有效但未指定状态;只能重新赋值或销毁,不要读取它的值。
|
|
87
|
+
- 移动构造函数和移动赋值标记 `noexcept`,否则 `std::vector` 等容器在扩容时可能选择拷贝。
|
|
88
|
+
- 返回值直接 `return value;`,不要 `return std::move(value);`;后者阻止 RVO。
|
|
89
|
+
|
|
90
|
+
## 模板、概念与 CTAD
|
|
91
|
+
|
|
92
|
+
- 模板定义放头文件;`.cpp` 里的模板实例化只对当前翻译单元可见。
|
|
93
|
+
- C++20 用概念约束模板参数,把错误从实例化深处提前到调用点:
|
|
94
|
+
|
|
95
|
+
```cpp
|
|
96
|
+
template <typename T>
|
|
97
|
+
concept Numeric = std::is_arithmetic_v<T>;
|
|
98
|
+
|
|
99
|
+
template <Numeric T>
|
|
100
|
+
T Clamp(T value, T low, T high) {
|
|
101
|
+
if (value < low) return low;
|
|
102
|
+
if (value > high) return high;
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- 概念只表达语法和语义约束,不替代文档;错误消息要能让调用者知道违反了哪条约束。
|
|
108
|
+
- CTAD(类模板实参推导)用于局部变量和明显场景:`std::pair pair{1, 2.0};`;公共接口写显式模板实参,避免推导规则变化影响契约。
|
|
109
|
+
- 不用模板做运行期分支;需要运行期多态时用虚函数或 `std::variant`。
|
|
110
|
+
|
|
111
|
+
## 检查清单
|
|
112
|
+
|
|
113
|
+
- [ ] 初始化方式统一,窄化转换被编译器拦住。
|
|
114
|
+
- [ ] `auto` 只出现在类型显然的局部变量;公共接口有显式类型。
|
|
115
|
+
- [ ] 共享常量用 `inline constexpr`,没有宏常量。
|
|
116
|
+
- [ ] 比较不是 Yoda 形式,混合优先级有括号。
|
|
117
|
+
- [ ] 资源由 RAII 管理,没有裸 `new` / `delete`。
|
|
118
|
+
- [ ] 特殊成员声明与定义位置正确,移动操作 `noexcept`。
|
|
119
|
+
- [ ] 模板约束用概念表达,定义在头文件。
|