cpp-algo C++ 编码风格指南
本指南以 MaaFramework 源码为标杆,针对 cpp-algo 中已知的工程缺陷制定规范。
1. 命名规范
cpp-algo 当前最严重的问题是 命名风格不统一,同一个项目中混用了多种约定。
必须遵循的规则
| 元素 | 风格 | 示例 |
|---|---|---|
| 类 / 结构体 / 枚举 | PascalCase | MapLocator, MatchFeature |
| 成员函数 | camelCase | initialize(), resetTrackingState() |
| 自由函数 | PascalCase(对外)/ snake_case(内部工具) | CreateInputBackend() |
| 成员变量 | snake*case + 尾下划线 * |
locator_, current_zone_id_ |
| 局部变量 | snake_case | search_rect, fine_result |
| 常量(constexpr) | k 前缀 + PascalCase |
kDefaultMinimapRoi, kMaxLostFrames |
| 宏 | ALL_CAPS | MAA_TRUE, LOG_ARGS |
| 命名空间 | lowercase | maplocator, mapnavigator |
| 模板参数 | 后缀 _t 或 PascalCase |
Item_t, OutT |
| 枚举值 | PascalCase | TrackingLost, ScreenBlocked |
已知违规(修改时顺手修复)
MapPosition中zoneId、sliceIndex等用了 camelCase 成员,应改为zone_id_、slice_index_TrackingConfig中maxNormalSpeed、screenBlockedThreshold同理MatchStrategy中_isBase(前导下划线)应改为is_base_- 常量
MinimapROIOriginX、MaxLostTrackingCount、MinMatchScore缺少k前缀
2. 头文件规范
Include Guard
统一使用 #pragma once(已做到)。
Include 顺序
按以下分组排列,组间空行分隔:
- 本
.cpp对应的.h - C++ 标准库
<algorithm>,<string>... - 第三方库
<opencv2/...>,<meojson/json.hpp>,<onnxruntime/...> - MaaFramework
<MaaFramework/...>,<MaaUtils/...> - 本项目头文件
"MapTypes.h","../utils.h"
OpenCV 引入
必须 通过 <MaaUtils/NoWarningCV.hpp> 引入 OpenCV,禁止直接 <opencv2/opencv.hpp>。这是 MaaFramework 的统一做法,用于抑制编译器警告。
当前违规文件:MatchStrategy.h、MotionTracker.h、YoloPredictor.h 直接引入了 <opencv2/opencv.hpp>。
MaaFramework 头文件引号
对外部依赖(MaaFramework、第三方库)统一使用尖括号 <>,对本项目内部头文件使用双引号 ""。
3. 公共工具复用(消除重复代码)
cpp-algo 中存在多处 重复实现,必须提取到公共头文件。
必须提取的工具
ScopedImageBuffer — 当前在 MapLocateAction.cpp、position_provider.cpp、adb_input_backend.cpp 三处重复定义,应提取到公共头文件(如 source/common/scoped_buffer.h):
class ScopedImageBuffer
{
public:
ScopedImageBuffer() : buffer_(MaaImageBufferCreate()) {}
~ScopedImageBuffer() { MaaImageBufferDestroy(buffer_); }
ScopedImageBuffer(const ScopedImageBuffer&) = delete;
ScopedImageBuffer& operator=(const ScopedImageBuffer&) = delete;
MaaImageBuffer* Get() const { return buffer_; }
private:
MaaImageBuffer* buffer_;
};
DetectControllerType — 当前在 MapLocateAction.cpp、position_provider.cpp、backend.cpp 三处重复实现,应提取到 controller_type_utils.h。
MAA_TRUE / MAA_FALSE 宏 — 在多个 .cpp 中条件定义。应在一个公共头文件中统一处理,或直接使用 MaaBool 的 1 / 0。
新增公共工具的原则
- 在
source/common/下建立公共头文件 - 跨模块(MapLocator / MapNavigator)共用的工具放这里
- 模块内部工具放在模块自己的匿名命名空间或
detail命名空间中
4. 命名空间
正确做法
- 顶层按模块分:
maplocator、mapnavigator - 后端按层级嵌套:
mapnavigator::backend::adb - 实现细节用匿名命名空间(
namespace { }在.cpp中)
避免的问题
utils::SleepFor 嵌套在 mapnavigator 命名空间中(navi_math.h),而 source/utils.h 是另一个全局工具头。这造成了命名空间语义冲突。应将通用工具统一放入 source/common/。
5. 类设计
推荐模式(参考 MaaFramework)
- PIMPL:对外暴露的复杂类使用 PIMPL 隐藏实现(
MapLocator已正确使用) - NonCopyable:需要禁止拷贝的类应明确
= delete拷贝构造和赋值(ScopedImageBuffer已做到,但建议提取基类或用宏) - Strategy 模式:
IMatchStrategy+ Factory 的设计是好的,保持 - RAII:资源获取即初始化,析构时释放(
ScopedImageBuffer是好例子)
需要改进的点
NavigationStateMachine持有多个裸指针(ActionWrapper*等),生命周期依赖调用者保证——应添加注释说明所有权语义,或使用std::shared_ptr/std::weak_ptr- 全局单例
getOrInitLocator()使用static std::shared_ptr——可接受但应注意线程安全和测试性
6. 日志规范
遵循 maa-logging skill 的完整指南。此处强调 cpp-algo 特有的问题:
禁止高频大量日志
YOLO 推理中每帧输出完整 softmax 向量是 严重性能问题:
// 错误 — 每帧打印完整分类向量
LogInfo << "YOLO Raw All:" << yoloClassNames << std::vector<float>(...);
// 正确 — 仅输出关键结果,详细信息用 LogTrace
LogDebug << "YOLO:" << VAR(predicted_name) << VAR(max_conf);
LogTrace << "YOLO all scores:" << scores;
日志级别选择
| 场景 | 级别 |
|---|---|
| 初始化成功/失败、关键状态变更 | LogInfo |
| 定位结果、导航阶段切换 | LogInfo |
| 匹配分数、中间计算 | LogDebug |
| 完整矩阵/向量数据 | LogTrace |
| 可恢复异常(追踪丢失) | LogWarn |
| 不可恢复错误 | LogError |
7. 错误处理
模式
- 返回
bool/std::optional表示成功/失败 - 失败路径
LogError+ 早期return - OpenCV 操作用
try/catch保护(CoreMatch中已有,应推广到其他 cv 调用密集处) - MaaFramework C API 返回值必须检查
禁止
- 静默忽略错误
- 假设指针非空而不检查
8. 现代 C++ 用法
项目目标 C++20,应积极使用现代特性:
| 推荐 | 示例 |
|---|---|
std::optional |
返回可能失败的结果 |
| 指定初始化器 | LocateResult { .status = ..., .position = ... } |
std::filesystem |
路径操作 |
std::format |
字符串格式化(替代 std::stringstream) |
std::ranges |
容器算法链(controller_type_utils.h 中已用) |
constexpr |
编译期常量 |
| 结构化绑定 | auto [x, y] = getPosition(); |
| smart pointers | std::unique_ptr / std::shared_ptr 管理资源 |
X-Macro 的使用
NAVI_ACTION_TYPES(X) 宏用于生成枚举和字符串映射。这种模式可以接受,但应:
- 在宏定义处添加注释解释用途
- 确保使用
#undef清理临时宏
9. 魔法数字
cpp-algo 中散布大量硬编码阈值(0.43、4.5、0.85、0.55 等)。
规则
- 所有阈值 必须定义为
constexpr命名常量,带k前缀 - 常量定义集中放在对应模块的 config 结构体或头文件顶部
- 必须附带注释说明物理含义和调优依据
// 错误
if (score < 0.55) { return false; }
// 正确
constexpr double kGlobalSearchPassThreshold = 0.55; // 全局搜索及格线,容忍 UI 遮挡 + 光影
if (score < kGlobalSearchPassThreshold) { return false; }
10. CMake 规范
禁止 file(GLOB_RECURSE)
当前 source/CMakeLists.txt 使用 file(GLOB_RECURSE) 自动收集源文件。CMake 官方文档明确不推荐此做法(新增/删除文件不会触发重新配置)。
应改为显式列出源文件:
target_sources(cpp-algo PRIVATE
main.cpp
MapLocator/MapLocator.cpp
MapLocator/MapLocateAction.cpp
# ...
)
清理未使用变量
${cpp_algo_header} 从未定义却被引用,应清除。
11. 文件命名
| 元素 | 风格 | 示例 |
|---|---|---|
| 类对应的文件 | PascalCase | MapLocator.h, MapLocator.cpp |
| 工具/非类文件 | snake_case | controller_type_utils.h, navi_math.h |
| 目录 | PascalCase(模块)/ snake_case(工具) | MapLocator/, Backend/Adb/ |
当前 my_reco_1/ 是示例模板目录,如果保留应重命名为有意义的名称。
12. 注释语言
- 代码注释使用 中文 或 英文 均可,但单个文件内保持一致
- 对外接口(
.h中的 public 方法)建议英文注释 - 算法实现细节(
.cpp中)用中文注释解释"为什么"是可以的(当前做得好的部分)
审查清单
修改 cpp-algo 代码时,对照检查:
- 命名风格是否符合上表
- 是否引入了重复代码(检查是否已有公共工具)
- OpenCV 是否通过
NoWarningCV.hpp引入 - 日志级别是否合理,是否避免了高频大量输出
- 新常量是否有
k前缀和注释 - 错误路径是否有日志和合理返回值
- 新文件是否加入了 CMakeLists.txt 的显式列表(如已迁移)
详细的命名对照和重构示例见 reference.md。