面向中大型 C++ 项目,适用于机器人、自动驾驶、AI 推理部署、嵌入式、音视频、高性能服务和 SDK 开发。
目录
- 设计目标
- 工程目录结构
- 模块划分
- 接口设计
- 依赖管理
- CMake 规范
- 配置管理
- 日志与错误处理
- 并发设计
- 资源管理
- 测试策略
- 性能与可观测性
- API/ABI 兼容
- 代码规范
- CI/CD 与发布
- 参考工程骨架
1. 设计目标
一个可维护的 C++ 工程应具备:
- 模块边界清晰;
- 依赖方向稳定;
- 接口最小化;
- 生命周期明确;
- 错误可诊断;
- 配置集中管理;
- 易测试;
- 可移植;
- 可扩展;
- 性能可测量。
核心原则:
text
高内聚
低耦合
单向依赖
接口隔离
资源自动管理
配置与代码分离
2. 推荐目录结构
text
project/
├── apps/ # 可执行程序入口
│ └── demo_app/
│ └── main.cpp
├── include/ # 对外公开头文件
│ └── project/
│ ├── detector.h
│ └── version.h
├── src/ # 内部实现
│ ├── detector.cpp
│ └── detector_impl.cpp
├── modules/ # 大型工程的业务模块
│ ├── capture/
│ ├── preprocessing/
│ ├── inference/
│ ├── postprocessing/
│ └── decision/
├── config/ # 配置文件
├── tests/
│ ├── unit/
│ ├── integration/
│ └── benchmark/
├── tools/ # 辅助工具
├── scripts/ # 构建、部署、数据处理脚本
├── docs/
├── third_party/
├── cmake/
├── CMakeLists.txt
├── CMakePresets.json
├── .clang-format
├── .clang-tidy
└── README.md
公开头文件和内部实现应分离,避免业务方依赖私有细节。
3. 分层架构
推荐分层:
#mermaid-svg-FbHsWtJIcpWr2qMP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FbHsWtJIcpWr2qMP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FbHsWtJIcpWr2qMP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FbHsWtJIcpWr2qMP .error-icon{fill:#552222;}#mermaid-svg-FbHsWtJIcpWr2qMP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FbHsWtJIcpWr2qMP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FbHsWtJIcpWr2qMP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FbHsWtJIcpWr2qMP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FbHsWtJIcpWr2qMP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FbHsWtJIcpWr2qMP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FbHsWtJIcpWr2qMP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FbHsWtJIcpWr2qMP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FbHsWtJIcpWr2qMP .marker.cross{stroke:#333333;}#mermaid-svg-FbHsWtJIcpWr2qMP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FbHsWtJIcpWr2qMP p{margin:0;}#mermaid-svg-FbHsWtJIcpWr2qMP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FbHsWtJIcpWr2qMP .cluster-label text{fill:#333;}#mermaid-svg-FbHsWtJIcpWr2qMP .cluster-label span{color:#333;}#mermaid-svg-FbHsWtJIcpWr2qMP .cluster-label span p{background-color:transparent;}#mermaid-svg-FbHsWtJIcpWr2qMP .label text,#mermaid-svg-FbHsWtJIcpWr2qMP span{fill:#333;color:#333;}#mermaid-svg-FbHsWtJIcpWr2qMP .node rect,#mermaid-svg-FbHsWtJIcpWr2qMP .node circle,#mermaid-svg-FbHsWtJIcpWr2qMP .node ellipse,#mermaid-svg-FbHsWtJIcpWr2qMP .node polygon,#mermaid-svg-FbHsWtJIcpWr2qMP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FbHsWtJIcpWr2qMP .rough-node .label text,#mermaid-svg-FbHsWtJIcpWr2qMP .node .label text,#mermaid-svg-FbHsWtJIcpWr2qMP .image-shape .label,#mermaid-svg-FbHsWtJIcpWr2qMP .icon-shape .label{text-anchor:middle;}#mermaid-svg-FbHsWtJIcpWr2qMP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FbHsWtJIcpWr2qMP .rough-node .label,#mermaid-svg-FbHsWtJIcpWr2qMP .node .label,#mermaid-svg-FbHsWtJIcpWr2qMP .image-shape .label,#mermaid-svg-FbHsWtJIcpWr2qMP .icon-shape .label{text-align:center;}#mermaid-svg-FbHsWtJIcpWr2qMP .node.clickable{cursor:pointer;}#mermaid-svg-FbHsWtJIcpWr2qMP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FbHsWtJIcpWr2qMP .arrowheadPath{fill:#333333;}#mermaid-svg-FbHsWtJIcpWr2qMP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FbHsWtJIcpWr2qMP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FbHsWtJIcpWr2qMP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FbHsWtJIcpWr2qMP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FbHsWtJIcpWr2qMP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FbHsWtJIcpWr2qMP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FbHsWtJIcpWr2qMP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FbHsWtJIcpWr2qMP .cluster text{fill:#333;}#mermaid-svg-FbHsWtJIcpWr2qMP .cluster span{color:#333;}#mermaid-svg-FbHsWtJIcpWr2qMP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FbHsWtJIcpWr2qMP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FbHsWtJIcpWr2qMP rect.text{fill:none;stroke-width:0;}#mermaid-svg-FbHsWtJIcpWr2qMP .icon-shape,#mermaid-svg-FbHsWtJIcpWr2qMP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FbHsWtJIcpWr2qMP .icon-shape p,#mermaid-svg-FbHsWtJIcpWr2qMP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FbHsWtJIcpWr2qMP .icon-shape .label rect,#mermaid-svg-FbHsWtJIcpWr2qMP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FbHsWtJIcpWr2qMP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FbHsWtJIcpWr2qMP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FbHsWtJIcpWr2qMP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Application
Orchestration / Service
Domain / Core
Infrastructure Adapter
OS / Hardware / Third-party
Application
负责:
- 参数解析;
- 启动和关闭;
- 服务注册;
- 顶层流程编排。
Service / Orchestration
负责:
- 用例流程;
- 模块组合;
- 状态机;
- 任务调度。
Domain / Core
负责:
- 核心业务模型;
- 决策规则;
- 算法接口;
- 与平台无关的逻辑。
Infrastructure
负责:
- 文件系统;
- 网络;
- 数据库;
- 相机、雷达、串口;
- ONNX/MNN/TensorRT;
- ROS2;
- 操作系统接口。
依赖方向应由上层指向抽象,而不是直接耦合具体实现。
4. 模块设计原则
4.1 单一职责
一个模块只负责一类变化原因。
错误示例:
cpp
class Detector {
public:
void loadConfig();
void openCamera();
void infer();
void saveDatabase();
void sendHttpRequest();
};
职责过多。
改进:
cpp
class IFrameSource;
class IDetector;
class IResultStore;
class IResultPublisher;
4.2 接口隔离
避免巨大接口:
cpp
class ISystem {
public:
virtual void capture() = 0;
virtual void infer() = 0;
virtual void save() = 0;
virtual void upload() = 0;
};
拆分为更小接口:
cpp
class IFrameSource {
public:
virtual ~IFrameSource() = default;
virtual Frame read() = 0;
};
class IInferenceEngine {
public:
virtual ~IInferenceEngine() = default;
virtual InferenceResult infer(const Tensor& input) = 0;
};
4.3 依赖倒置
核心业务不应直接依赖具体推理引擎:
cpp
class DetectorService {
public:
explicit DetectorService(std::unique_ptr<IInferenceEngine> engine)
: engine_(std::move(engine)) {
}
private:
std::unique_ptr<IInferenceEngine> engine_;
};
可切换:
- ONNX Runtime;
- MNN;
- TensorRT;
- OpenVINO。
5. 接口设计规范
5.1 输入输出对象
避免参数过多:
cpp
DetectionResult detect(
const Image& image,
int width,
int height,
float threshold,
bool enable_nms,
int max_count);
推荐请求对象:
cpp
struct DetectionRequest {
ImageView image;
float score_threshold{0.5F};
float nms_threshold{0.45F};
int max_detections{100};
};
struct DetectionResult {
Status status;
std::vector<Detection> detections;
TimingInfo timing;
};
5.2 const 正确性
只读输入:
cpp
Result process(const Request& request);
不会修改对象的方法:
cpp
std::size_t size() const noexcept;
5.3 生命周期表达
推荐:
cpp
void process(const Frame& frame); // 非拥有借用
std::unique_ptr<IEngine> createEngine(); // 转移唯一所有权
std::shared_ptr<Model> model(); // 共享所有权
不应只返回裸指针而不说明所有权。
5.4 禁止接口直接暴露实现类型
不推荐:
cpp
Ort::Session* session();
推荐:
cpp
class IInferenceSession {
public:
virtual ~IInferenceSession() = default;
virtual Result run(const TensorMap& inputs) = 0;
};
5.5 错误返回
可选择:
状态对象
cpp
struct Status {
int code{0};
std::string message;
bool ok() const noexcept {
return code == 0;
}
};
std::optional
适合"可能没有值,但没有详细错误"的场景。
std::expected
适合同时返回结果和错误信息:
cpp
std::expected<Model, Error> loadModel(const Path& path);
异常
适合构造失败、不可恢复错误和跨多层传播,但需要统一项目策略。
不要在同一层混用多种错误模型而无明确规范。
6. 依赖管理
6.1 依赖方向
text
app
↓
service
↓
core
↑
adapter
基础设施实现依赖核心接口,核心不依赖具体平台。
6.2 头文件依赖最小化
优先前置声明:
cpp
class Model;
class Detector {
public:
Detector();
~Detector();
private:
std::unique_ptr<Model> model_;
};
实现放在 .cpp 中。
6.3 PImpl
cpp
class Detector {
public:
Detector();
~Detector();
Detector(Detector&&) noexcept;
Detector& operator=(Detector&&) noexcept;
Result infer(const Image& image);
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
适合 SDK、动态库和编译依赖较重的模块。
6.4 第三方库隔离
第三方类型不要扩散到业务接口。
错误:
cpp
cv::Mat preprocess(const cv::Mat& image);
若业务层不希望依赖 OpenCV,可使用自己的 ImageView。
7. CMake 规范
7.1 基本模板
cmake
cmake_minimum_required(VERSION 3.20)
project(
cpp_project
VERSION 1.0.0
LANGUAGES CXX
)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
7.2 使用 target 级配置
cmake
add_library(project_core
src/detector.cpp
)
target_include_directories(project_core
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
target_compile_features(project_core
PUBLIC cxx_std_17
)
不推荐大量使用全局:
cmake
include_directories(...)
add_definitions(...)
7.3 警告选项
cmake
if(MSVC)
target_compile_options(project_core PRIVATE /W4 /permissive-)
else()
target_compile_options(project_core PRIVATE
-Wall
-Wextra
-Wpedantic
-Wconversion
-Wshadow
)
endif()
可在 CI 中将核心模块警告视为错误。
7.4 Debug Sanitizer
cmake
option(ENABLE_ASAN "Enable AddressSanitizer" OFF)
if(ENABLE_ASAN AND NOT MSVC)
target_compile_options(project_core PRIVATE
-fsanitize=address
-fno-omit-frame-pointer
)
target_link_options(project_core PRIVATE
-fsanitize=address
)
endif()
7.5 安装与导出
cmake
install(
TARGETS project_core
EXPORT projectTargets
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
install(
DIRECTORY include/
DESTINATION include
)
SDK 工程应提供:
- 安装规则;
- CMake package;
- 版本文件;
- 示例程序;
- ABI 说明。
8. 配置管理
8.1 配置层级
推荐:
text
默认值
↓
配置文件
↓
环境变量
↓
命令行参数
优先级从下到上覆盖。
8.2 配置对象
cpp
struct InferenceConfig {
std::string model_path;
std::string backend{"onnxruntime"};
int num_threads{4};
float score_threshold{0.5F};
bool enable_fp16{false};
};
配置加载后应进行完整校验:
cpp
Status validate(const InferenceConfig& config);
8.3 禁止魔法数字
错误:
cpp
if (score > 0.73F) {
}
推荐:
cpp
if (score > config.score_threshold) {
}
8.4 配置版本
复杂系统建议加入:
yaml
config_version: 2
配置升级时提供迁移或兼容策略。
9. 日志规范
9.1 日志等级
- TRACE:细粒度追踪;
- DEBUG:开发调试;
- INFO:关键流程;
- WARN:可恢复异常;
- ERROR:功能失败;
- CRITICAL/FATAL:无法继续运行。
9.2 日志内容
日志应包含:
- 模块名;
- 任务 ID;
- 时间;
- 输入摘要;
- 错误码;
- 关键耗时;
- 上下文。
示例:
text
[INFO] [m3.registry] request_id=abc123 sku=cola
action=reload result=success elapsed_ms=17
9.3 禁止泄露敏感信息
避免记录:
- 密码;
- Token;
- 完整用户隐私数据;
- 原始生物识别信息;
- 大块二进制数据。
10. 错误处理规范
10.1 错误分层
text
底层错误
↓ 转换
模块错误
↓ 补充上下文
业务错误
↓
用户可理解结果
不要丢失原始错误原因。
10.2 不要在库代码中直接退出
不推荐:
cpp
if (!model_loaded) {
std::exit(1);
}
库应返回错误,由应用层决定:
- 重试;
- 降级;
- 退出;
- 切换后端。
11. 并发设计
11.1 线程模型先于代码
先明确:
- 哪些线程;
- 谁生产数据;
- 谁消费数据;
- 队列容量;
- 退出机制;
- 背压策略;
- 异常传播;
- 资源归属。
11.2 生产者消费者
#mermaid-svg-afMI5DSvPNpHU5bq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-afMI5DSvPNpHU5bq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-afMI5DSvPNpHU5bq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-afMI5DSvPNpHU5bq .error-icon{fill:#552222;}#mermaid-svg-afMI5DSvPNpHU5bq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-afMI5DSvPNpHU5bq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-afMI5DSvPNpHU5bq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-afMI5DSvPNpHU5bq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-afMI5DSvPNpHU5bq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-afMI5DSvPNpHU5bq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-afMI5DSvPNpHU5bq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-afMI5DSvPNpHU5bq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-afMI5DSvPNpHU5bq .marker.cross{stroke:#333333;}#mermaid-svg-afMI5DSvPNpHU5bq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-afMI5DSvPNpHU5bq p{margin:0;}#mermaid-svg-afMI5DSvPNpHU5bq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-afMI5DSvPNpHU5bq .cluster-label text{fill:#333;}#mermaid-svg-afMI5DSvPNpHU5bq .cluster-label span{color:#333;}#mermaid-svg-afMI5DSvPNpHU5bq .cluster-label span p{background-color:transparent;}#mermaid-svg-afMI5DSvPNpHU5bq .label text,#mermaid-svg-afMI5DSvPNpHU5bq span{fill:#333;color:#333;}#mermaid-svg-afMI5DSvPNpHU5bq .node rect,#mermaid-svg-afMI5DSvPNpHU5bq .node circle,#mermaid-svg-afMI5DSvPNpHU5bq .node ellipse,#mermaid-svg-afMI5DSvPNpHU5bq .node polygon,#mermaid-svg-afMI5DSvPNpHU5bq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-afMI5DSvPNpHU5bq .rough-node .label text,#mermaid-svg-afMI5DSvPNpHU5bq .node .label text,#mermaid-svg-afMI5DSvPNpHU5bq .image-shape .label,#mermaid-svg-afMI5DSvPNpHU5bq .icon-shape .label{text-anchor:middle;}#mermaid-svg-afMI5DSvPNpHU5bq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-afMI5DSvPNpHU5bq .rough-node .label,#mermaid-svg-afMI5DSvPNpHU5bq .node .label,#mermaid-svg-afMI5DSvPNpHU5bq .image-shape .label,#mermaid-svg-afMI5DSvPNpHU5bq .icon-shape .label{text-align:center;}#mermaid-svg-afMI5DSvPNpHU5bq .node.clickable{cursor:pointer;}#mermaid-svg-afMI5DSvPNpHU5bq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-afMI5DSvPNpHU5bq .arrowheadPath{fill:#333333;}#mermaid-svg-afMI5DSvPNpHU5bq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-afMI5DSvPNpHU5bq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-afMI5DSvPNpHU5bq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-afMI5DSvPNpHU5bq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-afMI5DSvPNpHU5bq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-afMI5DSvPNpHU5bq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-afMI5DSvPNpHU5bq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-afMI5DSvPNpHU5bq .cluster text{fill:#333;}#mermaid-svg-afMI5DSvPNpHU5bq .cluster span{color:#333;}#mermaid-svg-afMI5DSvPNpHU5bq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-afMI5DSvPNpHU5bq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-afMI5DSvPNpHU5bq rect.text{fill:none;stroke-width:0;}#mermaid-svg-afMI5DSvPNpHU5bq .icon-shape,#mermaid-svg-afMI5DSvPNpHU5bq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-afMI5DSvPNpHU5bq .icon-shape p,#mermaid-svg-afMI5DSvPNpHU5bq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-afMI5DSvPNpHU5bq .icon-shape .label rect,#mermaid-svg-afMI5DSvPNpHU5bq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-afMI5DSvPNpHU5bq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-afMI5DSvPNpHU5bq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-afMI5DSvPNpHU5bq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Capture Thread
Bounded Queue
Inference Thread
Result Queue
Postprocess Thread
有界队列可以防止内存无限增长。
11.3 退出控制
推荐:
cpp
std::atomic<bool> running{true};
C++20 可使用:
cpp
std::jthread
std::stop_token
退出时应:
- 停止生产;
- 通知等待线程;
- 清空或丢弃队列;
- 等待线程退出;
- 释放资源。
11.4 锁粒度
避免:
- 在锁内执行 I/O;
- 在锁内运行推理;
- 在锁内调用用户回调;
- 锁顺序不一致。
12. 资源管理
所有资源应由 RAII 对象管理:
- 文件;
- 动态库;
- 内存;
- Socket;
- 线程;
- GPU buffer;
- 推理 session;
- 设备句柄。
示例:
cpp
class SessionHandle {
public:
explicit SessionHandle(Session* session)
: session_(session) {
}
~SessionHandle() {
destroySession(session_);
}
private:
Session* session_{nullptr};
};
13. 测试规范
13.1 测试金字塔
text
端到端测试
集成测试
单元测试
单元测试数量最多,端到端测试数量最少。
13.2 单元测试
适合测试:
- 数学函数;
- 数据转换;
- 配置校验;
- 阈值逻辑;
- 后处理;
- 状态机;
- 序列化。
13.3 集成测试
适合测试:
- 模型加载;
- 文件系统;
- 数据库;
- 相机驱动;
- 网络服务;
- 多模块链路。
13.4 回归测试
AI/视觉项目应固定:
- 输入样本;
- 模型版本;
- 配置版本;
- 期望输出;
- 精度容差;
- 性能基线。
13.5 测试替身
通过接口注入 Mock:
cpp
class FakeEngine : public IInferenceEngine {
public:
InferenceResult infer(const Tensor& input) override {
return fixed_result_;
}
private:
InferenceResult fixed_result_;
};
14. 性能与可观测性
14.1 关键指标
- 总延迟;
- 各阶段耗时;
- 吞吐量;
- 队列长度;
- 内存峰值;
- GPU/CPU 利用率;
- 错误率;
- 重试次数;
- 丢帧率。
14.2 统一计时
cpp
class ScopedTimer {
public:
explicit ScopedTimer(std::string name)
: name_(std::move(name)),
start_(std::chrono::steady_clock::now()) {
}
~ScopedTimer() {
const auto elapsed =
std::chrono::steady_clock::now() - start_;
report(name_, elapsed);
}
private:
std::string name_;
std::chrono::steady_clock::time_point start_;
};
14.3 基准测试
不要用单次运行判断优化效果。应:
- 预热;
- 多轮执行;
- 固定输入;
- 记录均值、P50、P95、P99;
- 区分 Debug/Release;
- 固定 CPU 频率和线程数。
15. API 与 ABI 兼容
15.1 API 兼容
源代码层面调用方式不变。
15.2 ABI 兼容
已编译程序无需重新编译即可使用新库。
容易破坏 ABI 的变更:
- 修改类成员顺序;
- 新增虚函数;
- 修改虚函数顺序;
- 修改结构体大小;
- 修改编译器或标准库;
- 修改异常和 RTTI 配置。
SDK 推荐:
- PImpl;
- C 接口边界;
- 版本化符号;
- 明确编译器和运行库要求。
16. C 接口封装
对外 SDK 可提供稳定 C ABI:
c
#ifdef __cplusplus
extern "C" {
#endif
typedef void* detector_handle_t;
int detector_create(
const char* config_path,
detector_handle_t* out_handle);
int detector_infer(
detector_handle_t handle,
const unsigned char* image_data,
int width,
int height);
void detector_destroy(detector_handle_t handle);
#ifdef __cplusplus
}
#endif
内部仍可使用现代 C++ 实现。
17. 代码规范
17.1 命名示例
cpp
class InferenceEngine {
public:
bool initialize();
private:
int num_threads_{4};
};
推荐:
- 类型:PascalCase;
- 函数:camelCase 或 snake_case,项目统一;
- 成员变量:后缀
_; - 常量:
kMaxBatchSize; - 宏:全大写,仅在必要时使用。
17.2 头文件保护
cpp
#pragma once
或传统 include guard。
17.3 禁止 using namespace 出现在头文件
错误:
cpp
using namespace std;
它会污染所有包含者的命名空间。
17.4 参数传递
- 小型标量按值;
- 大对象只读用
const T&; - 需要获取所有权用
T或unique_ptr<T>; - 可空借用用
T*; - 输出尽量通过返回值表达。
17.5 noexcept
不抛异常的析构函数、移动操作和简单访问器应考虑 noexcept。
18. CI/CD
推荐流水线:
#mermaid-svg-h4q8JICgptv4Bvav{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-h4q8JICgptv4Bvav .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-h4q8JICgptv4Bvav .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-h4q8JICgptv4Bvav .error-icon{fill:#552222;}#mermaid-svg-h4q8JICgptv4Bvav .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-h4q8JICgptv4Bvav .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-h4q8JICgptv4Bvav .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-h4q8JICgptv4Bvav .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-h4q8JICgptv4Bvav .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-h4q8JICgptv4Bvav .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-h4q8JICgptv4Bvav .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-h4q8JICgptv4Bvav .marker{fill:#333333;stroke:#333333;}#mermaid-svg-h4q8JICgptv4Bvav .marker.cross{stroke:#333333;}#mermaid-svg-h4q8JICgptv4Bvav svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-h4q8JICgptv4Bvav p{margin:0;}#mermaid-svg-h4q8JICgptv4Bvav .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-h4q8JICgptv4Bvav .cluster-label text{fill:#333;}#mermaid-svg-h4q8JICgptv4Bvav .cluster-label span{color:#333;}#mermaid-svg-h4q8JICgptv4Bvav .cluster-label span p{background-color:transparent;}#mermaid-svg-h4q8JICgptv4Bvav .label text,#mermaid-svg-h4q8JICgptv4Bvav span{fill:#333;color:#333;}#mermaid-svg-h4q8JICgptv4Bvav .node rect,#mermaid-svg-h4q8JICgptv4Bvav .node circle,#mermaid-svg-h4q8JICgptv4Bvav .node ellipse,#mermaid-svg-h4q8JICgptv4Bvav .node polygon,#mermaid-svg-h4q8JICgptv4Bvav .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-h4q8JICgptv4Bvav .rough-node .label text,#mermaid-svg-h4q8JICgptv4Bvav .node .label text,#mermaid-svg-h4q8JICgptv4Bvav .image-shape .label,#mermaid-svg-h4q8JICgptv4Bvav .icon-shape .label{text-anchor:middle;}#mermaid-svg-h4q8JICgptv4Bvav .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-h4q8JICgptv4Bvav .rough-node .label,#mermaid-svg-h4q8JICgptv4Bvav .node .label,#mermaid-svg-h4q8JICgptv4Bvav .image-shape .label,#mermaid-svg-h4q8JICgptv4Bvav .icon-shape .label{text-align:center;}#mermaid-svg-h4q8JICgptv4Bvav .node.clickable{cursor:pointer;}#mermaid-svg-h4q8JICgptv4Bvav .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-h4q8JICgptv4Bvav .arrowheadPath{fill:#333333;}#mermaid-svg-h4q8JICgptv4Bvav .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-h4q8JICgptv4Bvav .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-h4q8JICgptv4Bvav .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-h4q8JICgptv4Bvav .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-h4q8JICgptv4Bvav .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-h4q8JICgptv4Bvav .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-h4q8JICgptv4Bvav .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-h4q8JICgptv4Bvav .cluster text{fill:#333;}#mermaid-svg-h4q8JICgptv4Bvav .cluster span{color:#333;}#mermaid-svg-h4q8JICgptv4Bvav div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-h4q8JICgptv4Bvav .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-h4q8JICgptv4Bvav rect.text{fill:none;stroke-width:0;}#mermaid-svg-h4q8JICgptv4Bvav .icon-shape,#mermaid-svg-h4q8JICgptv4Bvav .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-h4q8JICgptv4Bvav .icon-shape p,#mermaid-svg-h4q8JICgptv4Bvav .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-h4q8JICgptv4Bvav .icon-shape .label rect,#mermaid-svg-h4q8JICgptv4Bvav .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-h4q8JICgptv4Bvav .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-h4q8JICgptv4Bvav .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-h4q8JICgptv4Bvav :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 提交代码
格式检查
静态分析
多平台编译
单元测试
Sanitizer
集成测试
打包发布
建议包含:
- clang-format;
- clang-tidy;
- 编译警告;
- Debug/Release;
- GCC/Clang;
- ASan/UBSan/TSan;
- 单元测试;
- 覆盖率;
- 制品归档。
19. 参考工程骨架
19.1 接口
cpp
class IPipelineStage {
public:
virtual ~IPipelineStage() = default;
virtual Status initialize(const Config& config) = 0;
virtual StageResult process(const StageInput& input) = 0;
virtual void shutdown() noexcept = 0;
};
19.2 流程编排
cpp
class Pipeline {
public:
Status addStage(std::unique_ptr<IPipelineStage> stage);
PipelineResult run(const PipelineInput& input);
private:
std::vector<std::unique_ptr<IPipelineStage>> stages_;
};
19.3 配置
yaml
pipeline:
enable_capture_check: true
enable_quality_check: true
enable_alignment: true
enable_recognition: true
enable_counting: true
enable_decision: true
runtime:
num_threads: 4
log_level: info
20. 架构评审清单
- 模块是否职责单一;
- 依赖是否单向;
- 第三方类型是否泄露到核心接口;
- 所有权是否清晰;
- 错误是否可诊断;
- 配置是否集中;
- 并发退出是否可控;
- 队列是否有界;
- 日志是否包含上下文;
- 是否支持单元测试;
- 是否存在全局可变状态;
- ABI 是否有兼容策略;
- 是否可启用 Sanitizer;
- 是否有性能基线;
- 是否能在目标平台复现构建。