C++ 工程架构设计规范

面向中大型 C++ 项目,适用于机器人、自动驾驶、AI 推理部署、嵌入式、音视频、高性能服务和 SDK 开发。


目录

  1. 设计目标
  2. 工程目录结构
  3. 模块划分
  4. 接口设计
  5. 依赖管理
  6. CMake 规范
  7. 配置管理
  8. 日志与错误处理
  9. 并发设计
  10. 资源管理
  11. 测试策略
  12. 性能与可观测性
  13. API/ABI 兼容
  14. 代码规范
  15. CI/CD 与发布
  16. 参考工程骨架

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

退出时应:

  1. 停止生产;
  2. 通知等待线程;
  3. 清空或丢弃队列;
  4. 等待线程退出;
  5. 释放资源。

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&
  • 需要获取所有权用 Tunique_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;
  • 是否有性能基线;
  • 是否能在目标平台复现构建。
相关推荐
C++ 老炮儿的技术栈1 小时前
基于MFC+原生GDI手动自绘仪表盘控件
c语言·c++·visual studio·控件·工业控制·gdi·自绘
自动化智库1 小时前
OpenCvSharp读取、显示和写入图像
机器人
孬甭_1 小时前
C++ vector
开发语言·c++
小肝一下1 小时前
3. 单链表
c语言·数据结构·c++·算法·leetcode·链表·dijkstra
小保CPP2 小时前
OpenCV C++提取webp动图里的图片
c++·人工智能·opencv·计算机视觉
光锥智能10 小时前
WAIC亮点|兼具泛化能力与作业效率的极智嘉机器人天团
人工智能·机器人
炸膛坦客10 小时前
单片机/C/C++八股:(二十六)IIC 专题(I²C)---- 上集
c语言·c++·单片机
旖-旎11 小时前
LeetCode 518:零钱兑换||(完全背包)—— 题解
c++·算法·leetcode·动态规划·背包问题
Henry Zhu12311 小时前
C++中的特殊成员函数与智能指针
c++