适用场景: 研究生阶段的个人/小团队项目,多模块C++开发
难度级别: 中级
预计阅读时间: 30分钟
最后更新: 2024年11月


📋 目录

  1. 为什么需要模块化
  2. 项目架构选择
  3. 目录结构设计
  4. CMake配置详解
  5. 依赖管理方案
  6. 模块开发流程
  7. 代码组织规范
  8. 实战案例
  9. 常见问题
  10. 进阶主题

1. 为什么需要模块化

问题场景

作为研究生,你可能面临这些挑战:

❌ 所有代码堆在一个文件夹
❌ 不同研究方向的代码混在一起
❌ 修改一处代码影响整个项目
❌ 无法独立测试某个功能
❌ 代码复用困难

模块化优势

✅ 清晰的代码组织结构
✅ 独立开发、测试、维护
✅ 代码复用性强
✅ 便于版本管理
✅ 支持选择性编译
✅ 易于扩展新功能

适用场景判断

适合模块化的项目:

  • 包含3个以上独立功能模块
  • 需要长期维护和扩展
  • 有代码复用需求
  • 涉及多个研究方向

不需要模块化的项目:

  • 一次性实验脚本
  • 功能单一的小工具
  • 快速原型验证

2. 项目架构选择

方案对比

架构类型适用场景优点缺点
单体仓库单文件夹项目简单直接难以扩展
多仓库独立子项目完全解耦管理复杂
Monorepo统一管理多模块易于共享代码
版本同步
需要学习成本

推荐方案:Monorepo

理由:

  1. 个人/小团队最优解 - 一个仓库管理所有模块
  2. 代码共享方便 - 公共代码放在common模块
  3. 依赖管理简单 - 统一的第三方库版本
  4. 重构友好 - 跨模块重构容易

3. 目录结构设计

推荐的标准结构

UAVResearchPlatform/              # 项目根目录
│
├── CMakeLists.txt                # 主CMake配置
├── vcpkg.json                    # 依赖管理(推荐)
├── README.md                     # 项目说明
├── LICENSE                       # 开源许可(可选)
│
├── docs/                         # 文档目录
│   ├── architecture.md           # 架构说明
│   ├── getting_started.md        # 快速开始
│   ├── api/                      # API文档
│   └── tutorials/                # 教程
│
├── external/                     # 第三方依赖(git submodule或源码)
│   ├── CMakeLists.txt
│   └── README.md
│
├── modules/                      # 核心模块目录
│   │
│   ├── common/                   # 公共基础库
│   │   ├── CMakeLists.txt
│   │   ├── include/
│   │   │   └── uav/common/
│   │   │       ├── types.h       # 通用类型定义
│   │   │       ├── math_utils.h  # 数学工具
│   │   │       ├── logging.h     # 日志系统
│   │   │       └── config.h      # 配置管理
│   │   ├── src/
│   │   │   ├── math_utils.cpp
│   │   │   ├── logging.cpp
│   │   │   └── config.cpp
│   │   ├── tests/                # 单元测试
│   │   │   └── test_math_utils.cpp
│   │   └── README.md
│   │
│   ├── trajectory/               # 轨迹规划模块
│   │   ├── CMakeLists.txt
│   │   ├── include/
│   │   │   └── uav/trajectory/
│   │   │       ├── dubins_path.h
│   │   │       ├── g2cbs_smoother.h
│   │   │       └── time_interpolator.h
│   │   ├── src/
│   │   │   ├── dubins_path.cpp
│   │   │   ├── g2cbs_smoother.cpp
│   │   │   └── time_interpolator.cpp
│   │   ├── tests/
│   │   │   └── test_dubins.cpp
│   │   ├── examples/
│   │   │   └── simple_trajectory.cpp
│   │   └── README.md
│   │
│   ├── terrain/                  # 地形处理模块
│   │   ├── CMakeLists.txt
│   │   ├── include/
│   │   │   └── uav/terrain/
│   │   │       ├── dem_chebyshev.h
│   │   │       └── terrain_analysis.h
│   │   └── src/
│   │       ├── dem_chebyshev.cpp
│   │       └── terrain_analysis.cpp
│   │
│   └── sar_simulation/           # SAR仿真模块
│       ├── CMakeLists.txt
│       ├── include/
│       │   └── uav/sar/
│       │       ├── sar_geometry.h
│       │       ├── otb_wrapper.h
│       │       └── coverage_planner.h
│       └── src/
│
├── apps/                         # 可执行应用程序
│   ├── trajectory_demo/
│   │   ├── CMakeLists.txt
│   │   └── main.cpp
│   ├── sar_mission/
│   │   ├── CMakeLists.txt
│   │   └── main.cpp
│   └── integrated_demo/
│       ├── CMakeLists.txt
│       └── main.cpp
│
├── tests/                        # 集成测试
│   ├── CMakeLists.txt
│   └── integration/
│       └── test_full_pipeline.cpp
│
├── scripts/                      # 工具脚本
│   ├── build.bat                 # Windows构建脚本
│   ├── build.sh                  # Linux构建脚本
│   ├── format_code.sh            # 代码格式化
│   └── visualize/                # 可视化脚本
│       └── plot_results.py
│
├── data/                         # 示例数据
│   ├── dem/
│   │   └── sample.tif
│   ├── missions/
│   │   └── example_mission.json
│   └── README.md
│
└── build/                        # 构建目录(git忽略)
    └── .gitkeep

目录设计原则

1. 模块独立性
每个模块应该:
✓ 有自己的CMakeLists.txt
✓ 有独立的include和src目录
✓ 有自己的测试和示例
✓ 有README.md说明文档
2. 公共代码提取
// modules/common/include/uav/common/types.h
namespace uav {
namespace common {

// 所有模块共用的基础类型
struct Point3D {
    double x, y, z;
};

struct Pose3D {
    Point3D position;
    double roll, pitch, yaw;
};

} // namespace common
} // namespace uav
3. 命名空间层次
推荐命名空间结构:
uav::common          - 公共基础
uav::trajectory      - 轨迹模块
uav::terrain         - 地形模块
uav::sar             - SAR模块

4. CMake配置详解

4.1 主CMakeLists.txt(根目录)

# UAVResearchPlatform/CMakeLists.txt
cmake_minimum_required(VERSION 3.15)

# 项目基本信息
project(UAVResearchPlatform 
    VERSION 1.0.0
    DESCRIPTION "UAV Research Platform for Graduate Students"
    LANGUAGES CXX
)

# ============================================
# 全局编译设置
# ============================================

# C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# 输出目录统一设置
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

# IDE文件夹组织(Visual Studio友好)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)

# ============================================
# 编译选项(可选模块)
# ============================================

option(BUILD_COMMON "Build common module" ON)
option(BUILD_TRAJECTORY "Build trajectory module" ON)
option(BUILD_TERRAIN "Build terrain module" ON)
option(BUILD_SAR "Build SAR simulation module" OFF)  # 默认关闭,需要OTB
option(BUILD_TESTS "Build unit tests" ON)
option(BUILD_EXAMPLES "Build example programs" ON)
option(BUILD_APPS "Build application programs" ON)
option(BUILD_DOCS "Build documentation" OFF)

# ============================================
# 第三方依赖查找
# ============================================

# 必需依赖
find_package(Eigen3 3.3 REQUIRED NO_MODULE)
message(STATUS "Found Eigen3: ${Eigen3_VERSION}")

# 可选依赖
if(BUILD_SAR)
    find_package(OTB REQUIRED)
    if(OTB_FOUND)
        message(STATUS "Found OTB: ${OTB_VERSION}")
    else()
        message(WARNING "OTB not found. SAR module will be disabled.")
        set(BUILD_SAR OFF)
    endif()
endif()

# 测试框架(推荐使用Catch2)
if(BUILD_TESTS)
    find_package(Catch2 CONFIG)
    if(NOT Catch2_FOUND)
        message(STATUS "Catch2 not found, tests will be disabled")
        set(BUILD_TESTS OFF)
    endif()
endif()

# ============================================
# 全局包含目录
# ============================================

# 让所有子项目都能找到模块的头文件
include_directories(
    ${CMAKE_SOURCE_DIR}/modules
)

# ============================================
# 添加子目录
# ============================================

# 按依赖顺序添加模块
if(BUILD_COMMON)
    add_subdirectory(modules/common)
endif()

if(BUILD_TRAJECTORY)
    add_subdirectory(modules/trajectory)
endif()

if(BUILD_TERRAIN)
    add_subdirectory(modules/terrain)
endif()

if(BUILD_SAR)
    add_subdirectory(modules/sar_simulation)
endif()

# 应用程序
if(BUILD_APPS)
    add_subdirectory(apps)
endif()

# 测试
if(BUILD_TESTS)
    enable_testing()
    add_subdirectory(tests)
endif()

# 文档
if(BUILD_DOCS)
    add_subdirectory(docs)
endif()

# ============================================
# 安装配置(高级功能,可选)
# ============================================

include(CMakePackageConfigHelpers)

# 生成版本文件
write_basic_package_version_file(
    "${CMAKE_CURRENT_BINARY_DIR}/UAVResearchPlatformConfigVersion.cmake"
    VERSION ${PROJECT_VERSION}
    COMPATIBILITY AnyNewerVersion
)

# 安装规则
install(FILES
    "${CMAKE_CURRENT_BINARY_DIR}/UAVResearchPlatformConfigVersion.cmake"
    DESTINATION lib/cmake/UAVResearchPlatform
)

# ============================================
# 打印配置摘要
# ============================================

message(STATUS "========================================")
message(STATUS "UAV Research Platform Configuration")
message(STATUS "========================================")
message(STATUS "Version: ${PROJECT_VERSION}")
message(STATUS "Build Type: ${CMAKE_BUILD_TYPE}")
message(STATUS "C++ Standard: ${CMAKE_CXX_STANDARD}")
message(STATUS "")
message(STATUS "Modules:")
message(STATUS "  Common: ${BUILD_COMMON}")
message(STATUS "  Trajectory: ${BUILD_TRAJECTORY}")
message(STATUS "  Terrain: ${BUILD_TERRAIN}")
message(STATUS "  SAR Simulation: ${BUILD_SAR}")
message(STATUS "")
message(STATUS "Options:")
message(STATUS "  Tests: ${BUILD_TESTS}")
message(STATUS "  Examples: ${BUILD_EXAMPLES}")
message(STATUS "  Apps: ${BUILD_APPS}")
message(STATUS "  Docs: ${BUILD_DOCS}")
message(STATUS "========================================")

4.2 模块CMakeLists.txt模板

# modules/trajectory/CMakeLists.txt
project(uav_trajectory)

# ============================================
# 源文件收集
# ============================================

set(HEADERS
    include/uav/trajectory/dubins_path.h
    include/uav/trajectory/g2cbs_smoother.h
    include/uav/trajectory/time_interpolator.h
)

set(SOURCES
    src/dubins_path.cpp
    src/g2cbs_smoother.cpp
    src/time_interpolator.cpp
)

# ============================================
# 创建库目标
# ============================================

add_library(${PROJECT_NAME} ${SOURCES} ${HEADERS})

# 现代CMake:使用别名(推荐)
add_library(uav::trajectory ALIAS ${PROJECT_NAME})

# ============================================
# 包含目录设置
# ============================================

target_include_directories(${PROJECT_NAME}
    PUBLIC
        # 构建时的包含路径
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
        # 安装后的包含路径
        $<INSTALL_INTERFACE:include>
    PRIVATE
        # 内部实现的包含路径
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# ============================================
# 链接依赖
# ============================================

target_link_libraries(${PROJECT_NAME}
    PUBLIC
        # 公开依赖(使用此库的人也需要)
        Eigen3::Eigen
        uav::common
    PRIVATE
        # 私有依赖(仅内部使用)
        # 例如:某些实现细节的库
)

# ============================================
# 编译选项
# ============================================

# 针对不同编译器的警告设置
target_compile_options(${PROJECT_NAME} PRIVATE
    $<$<CXX_COMPILER_ID:MSVC>:/W4 /WX->
    $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra -Wpedantic>
)

# 编译定义
target_compile_definitions(${PROJECT_NAME} PRIVATE
    $<$<CONFIG:Debug>:DEBUG_MODE>
    $<$<CONFIG:Release>:NDEBUG>
)

# ============================================
# IDE组织(Visual Studio文件夹)
# ============================================

set_target_properties(${PROJECT_NAME} PROPERTIES
    FOLDER "Modules"
    OUTPUT_NAME "uav_trajectory"
)

# 头文件在IDE中显示
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/include PREFIX "Header Files" FILES ${HEADERS})
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/src PREFIX "Source Files" FILES ${SOURCES})

# ============================================
# 示例程序
# ============================================

if(BUILD_EXAMPLES)
    add_subdirectory(examples)
endif()

# ============================================
# 单元测试
# ============================================

if(BUILD_TESTS)
    add_subdirectory(tests)
endif()

# ============================================
# 安装规则
# ============================================

# 安装库文件
install(TARGETS ${PROJECT_NAME}
    EXPORT ${PROJECT_NAME}Targets
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
    RUNTIME DESTINATION bin
    INCLUDES DESTINATION include
)

# 安装头文件
install(DIRECTORY include/
    DESTINATION include
    FILES_MATCHING PATTERN "*.h"
)

# 导出目标(供其他项目使用)
install(EXPORT ${PROJECT_NAME}Targets
    FILE ${PROJECT_NAME}Targets.cmake
    NAMESPACE uav::
    DESTINATION lib/cmake/${PROJECT_NAME}
)

4.3 应用程序CMakeLists.txt

# apps/trajectory_demo/CMakeLists.txt
project(trajectory_demo)

# 创建可执行文件
add_executable(${PROJECT_NAME}
    main.cpp
)

# 链接需要的模块
target_link_libraries(${PROJECT_NAME}
    PRIVATE
        uav::common
        uav::trajectory
)

# IDE文件夹组织
set_target_properties(${PROJECT_NAME} PROPERTIES
    FOLDER "Applications"
)

# 安装
install(TARGETS ${PROJECT_NAME}
    RUNTIME DESTINATION bin
)

4.4 测试CMakeLists.txt

# modules/trajectory/tests/CMakeLists.txt
project(trajectory_tests)

# 定义测试可执行文件
add_executable(${PROJECT_NAME}
    test_dubins.cpp
    test_g2cbs.cpp
)

# 链接测试框架和被测模块
target_link_libraries(${PROJECT_NAME}
    PRIVATE
        uav::trajectory
        Catch2::Catch2WithMain  # 或 Catch2::Catch2
)

# 添加测试
include(CTest)
include(Catch)  # 如果使用Catch2

catch_discover_tests(${PROJECT_NAME})

# 或者手动添加测试
# add_test(NAME TrajectoryTests COMMAND ${PROJECT_NAME})

# IDE文件夹
set_target_properties(${PROJECT_NAME} PROPERTIES
    FOLDER "Tests"
)

5. 依赖管理方案

方案一:vcpkg(强烈推荐)

5.1 安装vcpkg
# Windows
cd D:\dev
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
.\bootstrap-vcpkg.bat

# Linux/Mac
cd ~/dev
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
5.2 创建vcpkg.json
{
  "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
  "name": "uav-research-platform",
  "version": "1.0.0",
  "description": "UAV Research Platform for Graduate Students",
  "dependencies": [
    "eigen3",
    {
      "name": "fmt",
      "version>=": "10.0.0"
    },
    "spdlog",
    "nlohmann-json"
  ],
  "features": {
    "tests": {
      "description": "Build with testing support",
      "dependencies": [
        "catch2"
      ]
    },
    "visualization": {
      "description": "Enable visualization features",
      "dependencies": [
        "opencv"
      ]
    }
  },
  "builtin-baseline": "latest"
}
5.3 使用vcpkg构建
# Windows (PowerShell)
cmake -B build -S . `
  -DCMAKE_TOOLCHAIN_FILE=D:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake `
  -DVCPKG_TARGET_TRIPLET=x64-windows

cmake --build build --config Release

# Linux/Mac
cmake -B build -S . \
  -DCMAKE_TOOLCHAIN_FILE=~/dev/vcpkg/scripts/buildsystems/vcpkg.cmake

cmake --build build
5.4 在CMake中使用
# 主CMakeLists.txt
find_package(Eigen3 CONFIG REQUIRED)
find_package(fmt CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)

# 在目标中链接
target_link_libraries(my_target
    PRIVATE
        Eigen3::Eigen
        fmt::fmt
        spdlog::spdlog
        nlohmann_json::nlohmann_json
)

方案二:Git Submodule

# 添加子模块
git submodule add https://gitlab.com/libeigen/eigen.git external/eigen
git submodule add https://github.com/fmtlib/fmt.git external/fmt

# 初始化和更新
git submodule update --init --recursive
# external/CMakeLists.txt
# 添加子模块
add_subdirectory(eigen)
add_subdirectory(fmt)

# 可选:禁用子模块的测试和示例
set(BUILD_TESTING OFF CACHE BOOL "" FORCE)
set(FMT_TEST OFF CACHE BOOL "" FORCE)

依赖管理建议

方案优点缺点推荐度
vcpkg自动管理,版本统一
跨平台支持好
首次下载慢⭐⭐⭐⭐⭐
Git Submodule完全控制,离线可用手动管理麻烦⭐⭐⭐
系统安装简单直接版本冲突风险⭐⭐
手动复制最简单维护困难

6. 模块开发流程

工作流程图

1. 需求分析
   ↓
2. 模块设计(接口定义)
   ↓
3. 创建模块目录结构
   ↓
4. 编写头文件(接口)
   ↓
5. 编写实现文件
   ↓
6. 编写单元测试
   ↓
7. 编写示例程序
   ↓
8. 编写文档
   ↓
9. 集成到主项目

实战:创建新模块

步骤1:创建目录结构
cd UAVResearchPlatform/modules
mkdir -p sensor_fusion/{include/uav/sensor_fusion,src,tests,examples}
cd sensor_fusion
步骤2:编写头文件
// include/uav/sensor_fusion/kalman_filter.h
#ifndef UAV_SENSOR_FUSION_KALMAN_FILTER_H
#define UAV_SENSOR_FUSION_KALMAN_FILTER_H

#include <Eigen/Dense>
#include <uav/common/types.h>

namespace uav {
namespace sensor_fusion {

/**
 * @brief 扩展卡尔曼滤波器
 * @details 用于无人机状态估计
 */
class KalmanFilter {
public:
    /**
     * @brief 构造函数
     * @param state_dim 状态维度
     * @param meas_dim 测量维度
     */
    KalmanFilter(int state_dim, int meas_dim);
    
    /**
     * @brief 预测步骤
     * @param dt 时间间隔
     */
    void predict(double dt);
    
    /**
     * @brief 更新步骤
     * @param measurement 测量值
     */
    void update(const Eigen::VectorXd& measurement);
    
    /**
     * @brief 获取当前状态估计
     * @return 状态向量
     */
    Eigen::VectorXd getState() const { return x_; }
    
    /**
     * @brief 获取状态协方差
     * @return 协方差矩阵
     */
    Eigen::MatrixXd getCovariance() const { return P_; }
    
private:
    int state_dim_;      ///< 状态维度
    int meas_dim_;       ///< 测量维度
    
    Eigen::VectorXd x_;  ///< 状态向量
    Eigen::MatrixXd P_;  ///< 状态协方差
    Eigen::MatrixXd F_;  ///< 状态转移矩阵
    Eigen::MatrixXd H_;  ///< 观测矩阵
    Eigen::MatrixXd Q_;  ///< 过程噪声协方差
    Eigen::MatrixXd R_;  ///< 测量噪声协方差
};

} // namespace sensor_fusion
} // namespace uav

#endif // UAV_SENSOR_FUSION_KALMAN_FILTER_H
步骤3:编写实现文件
// src/kalman_filter.cpp
#include <uav/sensor_fusion/kalman_filter.h>

namespace uav {
namespace sensor_fusion {

KalmanFilter::KalmanFilter(int state_dim, int meas_dim)
    : state_dim_(state_dim), meas_dim_(meas_dim)
{
    // 初始化矩阵
    x_ = Eigen::VectorXd::Zero(state_dim_);
    P_ = Eigen::MatrixXd::Identity(state_dim_, state_dim_);
    F_ = Eigen::MatrixXd::Identity(state_dim_, state_dim_);
    H_ = Eigen::MatrixXd::Zero(meas_dim_, state_dim_);
    Q_ = Eigen::MatrixXd::Identity(state_dim_, state_dim_) * 0.01;
    R_ = Eigen::MatrixXd::Identity(meas_dim_, meas_dim_) * 0.1;
}

void KalmanFilter::predict(double dt) {
    // 状态预测: x = F * x
    x_ = F_ * x_;
    
    // 协方差预测: P = F * P * F^T + Q
    P_ = F_ * P_ * F_.transpose() + Q_;
}

void KalmanFilter::update(const Eigen::VectorXd& measurement) {
    // 创新(残差): y = z - H * x
    Eigen::VectorXd y = measurement - H_ * x_;
    
    // 创新协方差: S = H * P * H^T + R
    Eigen::MatrixXd S = H_ * P_ * H_.transpose() + R_;
    
    // 卡尔曼增益: K = P * H^T * S^(-1)
    Eigen::MatrixXd K = P_ * H_.transpose() * S.inverse();
    
    // 状态更新: x = x + K * y
    x_ = x_ + K * y;
    
    // 协方差更新: P = (I - K * H) * P
    Eigen::MatrixXd I = Eigen::MatrixXd::Identity(state_dim_, state_dim_);
    P_ = (I - K * H_) * P_;
}

} // namespace sensor_fusion
} // namespace uav
步骤4:编写单元测试
// tests/test_kalman_filter.cpp
#include <catch2/catch_test_macros.hpp>
#include <uav/sensor_fusion/kalman_filter.h>

using namespace uav::sensor_fusion;

TEST_CASE("KalmanFilter initialization", "[kalman]") {
    KalmanFilter kf(4, 2);
    
    REQUIRE(kf.getState().size() == 4);
    REQUIRE(kf.getCovariance().rows() == 4);
    REQUIRE(kf.getCovariance().cols() == 4);
}

TEST_CASE("KalmanFilter predict step", "[kalman]") {
    KalmanFilter kf(4, 2);
    
    // 预测前的状态
    Eigen::VectorXd state_before = kf.getState();
    
    // 执行预测
    kf.predict(0.1);
    
    // 检查状态已更新
    Eigen::VectorXd state_after = kf.getState();
    REQUIRE(state_after.size() == 4);
}

TEST_CASE("KalmanFilter update step", "[kalman]") {
    KalmanFilter kf(4, 2);
    
    // 模拟测量
    Eigen::VectorXd measurement(2);
    measurement << 1.0, 2.0;
    
    // 执行更新
    REQUIRE_NOTHROW(kf.update(measurement));
}
步骤5:创建CMakeLists.txt
# modules/sensor_fusion/CMakeLists.txt
project(uav_sensor_fusion)

# 源文件
set(HEADERS
    include/uav/sensor_fusion/kalman_filter.h
)

set(SOURCES
    src/kalman_filter.cpp
)

# 创建库
add_library(${PROJECT_NAME} ${SOURCES} ${HEADERS})
add_library(uav::sensor_fusion ALIAS ${PROJECT_NAME})

# 包含目录
target_include_directories(${PROJECT_NAME}
    PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
        $<INSTALL_INTERFACE:include>
)

# 链接依赖
target_link_libraries(${PROJECT_NAME}
    PUBLIC
        Eigen3::Eigen
        uav::common
)

# 测试
if(BUILD_TESTS)
    add_subdirectory(tests)
endif()
步骤6:集成到主项目
# 在主CMakeLists.txt中添加
option(BUILD_SENSOR_FUSION "Build sensor fusion module" ON)

if(BUILD_SENSOR_FUSION)
    add_subdirectory(modules/sensor_fusion)
endif()

7. 代码组织规范

7.1 命名规范

// 文件命名:snake_case
// kalman_filter.h, sensor_fusion.cpp

// 类名:PascalCase
class KalmanFilter {};
class SensorFusionManager {};

// 函数名:camelCase
void predictState();
double computeError();

// 变量名:snake_case
int state_dim;
double time_step;

// 常量:UPPER_CASE
const double MAX_VELOCITY = 100.0;
constexpr int DEFAULT_SIZE = 10;

// 成员变量:带下划线后缀
class MyClass {
private:
    int value_;
    double coefficient_;
};

// 命名空间:snake_case
namespace uav {
namespace sensor_fusion {
}
}

7.2 头文件保护

// 推荐方式1:include guard
#ifndef UAV_MODULE_FILENAME_H
#define UAV_MODULE_FILENAME_H

// 内容...

#endif // UAV_MODULE_FILENAME_H

// 推荐方式2:pragma once(现代编译器都支持)
#pragma once

// 内容...

7.3 包含顺序

// source.cpp
// 1. 对应的头文件
#include "my_class.h"

// 2. 项目内部头文件
#include <uav/common/types.h>
#include <uav/trajectory/dubins_path.h>

// 3. 第三方库头文件
#include <Eigen/Dense>
#include <fmt/format.h>

// 4. 标准库头文件
#include <string>
#include <vector>
#include <memory>

7.4 代码格式化

创建.clang-format文件:

# .clang-format
BasedOnStyle: Google
IndentWidth: 4
ColumnLimit: 100
NamespaceIndentation: None
BreakBeforeBraces: Attach
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
PointerAlignment: Left
ReferenceAlignment: Left

使用方法:

# 格式化单个文件
clang-format -i src/my_file.cpp

# 格式化整个项目
find . -name "*.cpp" -o -name "*.h" | xargs clang-format -i

7.5 注释规范

/**
 * @file kalman_filter.h
 * @brief 卡尔曼滤波器实现
 * @author Your Name
 * @date 2024-11-13
 */

namespace uav {
namespace sensor_fusion {

/**
 * @brief 扩展卡尔曼滤波器类
 * 
 * @details
 * 该类实现了扩展卡尔曼滤波算法,用于无人机状态估计。
 * 支持任意维度的状态空间和测量空间。
 * 
 * @example
 * @code
 * KalmanFilter kf(6, 3);  // 6维状态,3维测量
 * kf.predict(0.1);
 * kf.update(measurement);
 * auto state = kf.getState();
 * @endcode
 * 
 * @see Reference: "Probabilistic Robotics" by Thrun et al.
 */
class KalmanFilter {
public:
    /**
     * @brief 构造函数
     * @param state_dim 状态向量维度
     * @param meas_dim 测量向量维度
     * @throws std::invalid_argument 如果维度<=0
     */
    KalmanFilter(int state_dim, int meas_dim);
    
    // 简短的函数可以用单行注释
    /// 获取当前状态估计
    Eigen::VectorXd getState() const;
    
private:
    int state_dim_;  ///< 状态维度
    Eigen::VectorXd x_;  ///< 状态向量
};

} // namespace sensor_fusion
} // namespace uav

8. 实战案例

案例:构建一个完整的小项目

我们创建一个简单的"无人机路径跟踪"项目,包含两个模块:

目录结构
SimpleUAVProject/
├── CMakeLists.txt
├── vcpkg.json
├── modules/
│   ├── path/
│   │   ├── CMakeLists.txt
│   │   ├── include/path/line_path.h
│   │   └── src/line_path.cpp
│   └── controller/
│       ├── CMakeLists.txt
│       ├── include/controller/pid_controller.h
│       └── src/pid_controller.cpp
└── apps/
    └── demo/
        ├── CMakeLists.txt
        └── main.cpp
完整代码实现

1. 主CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(SimpleUAVProject VERSION 1.0.0)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Eigen3 3.3 REQUIRED NO_MODULE)

add_subdirectory(modules/path)
add_subdirectory(modules/controller)
add_subdirectory(apps/demo)

2. 路径模块

// modules/path/include/path/line_path.h
#pragma once
#include <Eigen/Dense>
#include <vector>

namespace uav {
namespace path {

class LinePath {
public:
    LinePath(const Eigen::Vector3d& start, const Eigen::Vector3d& end);
    
    Eigen::Vector3d getPosition(double t) const;
    Eigen::Vector3d getVelocity(double t) const;
    
    double getTotalLength() const { return total_length_; }
    
private:
    Eigen::Vector3d start_;
    Eigen::Vector3d end_;
    double total_length_;
};

} // namespace path
} // namespace uav
// modules/path/src/line_path.cpp
#include <path/line_path.h>

namespace uav {
namespace path {

LinePath::LinePath(const Eigen::Vector3d& start, const Eigen::Vector3d& end)
    : start_(start), end_(end)
{
    total_length_ = (end_ - start_).norm();
}

Eigen::Vector3d LinePath::getPosition(double t) const {
    // t ∈ [0, 1]
    return start_ + t * (end_ - start_);
}

Eigen::Vector3d LinePath::getVelocity(double t) const {
    return (end_ - start_).normalized();
}

} // namespace path
} // namespace uav
# modules/path/CMakeLists.txt
add_library(uav_path
    src/line_path.cpp
)

target_include_directories(uav_path
    PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
)

target_link_libraries(uav_path
    PUBLIC
        Eigen3::Eigen
)

3. 控制器模块

// modules/controller/include/controller/pid_controller.h
#pragma once
#include <Eigen/Dense>

namespace uav {
namespace controller {

class PIDController {
public:
    PIDController(double kp, double ki, double kd);
    
    Eigen::Vector3d compute(const Eigen::Vector3d& error, double dt);
    void reset();
    
private:
    double kp_, ki_, kd_;
    Eigen::Vector3d integral_;
    Eigen::Vector3d prev_error_;
};

} // namespace controller
} // namespace uav
// modules/controller/src/pid_controller.cpp
#include <controller/pid_controller.h>

namespace uav {
namespace controller {

PIDController::PIDController(double kp, double ki, double kd)
    : kp_(kp), ki_(ki), kd_(kd)
{
    reset();
}

Eigen::Vector3d PIDController::compute(const Eigen::Vector3d& error, double dt) {
    integral_ += error * dt;
    Eigen::Vector3d derivative = (error - prev_error_) / dt;
    prev_error_ = error;
    
    return kp_ * error + ki_ * integral_ + kd_ * derivative;
}

void PIDController::reset() {
    integral_ = Eigen::Vector3d::Zero();
    prev_error_ = Eigen::Vector3d::Zero();
}

} // namespace controller
} // namespace uav

4. 演示应用

// apps/demo/main.cpp
#include <iostream>
#include <path/line_path.h>
#include <controller/pid_controller.h>

int main() {
    using namespace uav;
    
    // 创建路径:从原点到(10, 10, 5)
    Eigen::Vector3d start(0, 0, 0);
    Eigen::Vector3d end(10, 10, 5);
    path::LinePath path(start, end);
    
    // 创建PID控制器
    controller::PIDController pid(1.0, 0.1, 0.5);
    
    // 模拟跟踪
    Eigen::Vector3d current_pos = start;
    double dt = 0.1;
    
    for (double t = 0; t <= 1.0; t += 0.1) {
        Eigen::Vector3d desired_pos = path.getPosition(t);
        Eigen::Vector3d error = desired_pos - current_pos;
        
        Eigen::Vector3d control = pid.compute(error, dt);
        current_pos += control * dt;
        
        std::cout << "t=" << t 
                  << " pos=(" << current_pos.transpose() << ")"
                  << " error=" << error.norm() << std::endl;
    }
    
    std::cout << "Path length: " << path.getTotalLength() << std::endl;
    
    return 0;
}
# apps/demo/CMakeLists.txt
add_executable(uav_demo
    main.cpp
)

target_link_libraries(uav_demo
    PRIVATE
        uav_path
        uav_controller
)
构建和运行
# Windows
mkdir build
cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=D:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake
cmake --build . --config Release
.\Release\uav_demo.exe

# Linux
mkdir build && cd build
cmake ..
make
./uav_demo

9. 常见问题

Q1: 找不到头文件

问题:

fatal error: uav/trajectory/dubins_path.h: No such file or directory

解决方案:

# 确保在主CMakeLists.txt中设置了包含路径
include_directories(${CMAKE_SOURCE_DIR}/modules)

# 或在目标中设置
target_include_directories(my_target
    PUBLIC
        ${CMAKE_SOURCE_DIR}/modules/trajectory/include
)

Q2: 链接错误

问题:

undefined reference to `uav::trajectory::DubinsPath::compute()`

解决方案:

# 确保链接了正确的库
target_link_libraries(my_app
    PRIVATE
        uav::trajectory  # 使用别名
)

Q3: Eigen相关错误

问题:

error: no member named 'Vector3d' in namespace 'Eigen'

解决方案:

# 确保找到并链接了Eigen
find_package(Eigen3 3.3 REQUIRED NO_MODULE)

target_link_libraries(my_target
    PUBLIC
        Eigen3::Eigen
)

Q4: vcpkg依赖安装失败

解决方案:

# 1. 清理缓存
vcpkg remove eigen3
vcpkg install eigen3

# 2. 使用国内镜像(如果在中国)
# 设置环境变量
export VCPKG_DOWNLOADS_MIRROR=https://mirrors.tuna.tsinghua.edu.cn/vcpkg/

# 3. 手动指定triplet
vcpkg install eigen3:x64-windows

Q5: Visual Studio找不到CMake项目

解决方案:

  1. 确保安装了"使用C++的桌面开发"工作负载
  2. 在Visual Studio中:文件 → 打开 → CMake → 选择CMakeLists.txt
  3. 配置CMake设置(CMakeSettings.json):
{
  "configurations": [
    {
      "name": "x64-Debug",
      "generator": "Ninja",
      "configurationType": "Debug",
      "buildRoot": "${projectDir}\\build\\${name}",
      "cmakeCommandArgs": "-DCMAKE_TOOLCHAIN_FILE=D:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake"
    }
  ]
}

Q6: 模块间循环依赖

问题:

模块A依赖模块B,模块B又依赖模块A

解决方案:

  1. 重新设计模块接口,消除循环依赖
  2. 提取公共部分到新模块
  3. 使用依赖倒置原则(Dependency Inversion)
错误设计:
trajectory → controller → trajectory (循环!)

正确设计:
common (接口定义)
  ↑         ↑
trajectory  controller

10. 进阶主题

10.1 跨平台编译

# 平台特定代码
if(WIN32)
    target_compile_definitions(my_target PRIVATE PLATFORM_WINDOWS)
elseif(UNIX AND NOT APPLE)
    target_compile_definitions(my_target PRIVATE PLATFORM_LINUX)
elseif(APPLE)
    target_compile_definitions(my_target PRIVATE PLATFORM_MACOS)
endif()

# 编译器特定选项
if(MSVC)
    target_compile_options(my_target PRIVATE /W4 /utf-8)
else()
    target_compile_options(my_target PRIVATE -Wall -Wextra)
endif()

10.2 配置文件管理

// include/uav/common/config.h.in
#ifndef UAV_COMMON_CONFIG_H
#define UAV_COMMON_CONFIG_H

#define PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@
#define PROJECT_VERSION "@PROJECT_VERSION@"

#cmakedefine ENABLE_LOGGING
#cmakedefine ENABLE_VISUALIZATION

#endif
# 配置文件生成
configure_file(
    ${CMAKE_SOURCE_DIR}/include/uav/common/config.h.in
    ${CMAKE_BINARY_DIR}/include/uav/common/config.h
)

10.3 性能分析

# 添加性能分析选项
option(ENABLE_PROFILING "Enable profiling" OFF)

if(ENABLE_PROFILING)
    if(MSVC)
        target_link_options(my_target PRIVATE /PROFILE)
    else()
        target_compile_options(my_target PRIVATE -pg)
        target_link_options(my_target PRIVATE -pg)
    endif()
endif()

10.4 单元测试覆盖率

# 代码覆盖率(GCC/Clang)
option(ENABLE_COVERAGE "Enable coverage reporting" OFF)

if(ENABLE_COVERAGE AND NOT MSVC)
    target_compile_options(my_target PRIVATE --coverage)
    target_link_options(my_target PRIVATE --coverage)
endif()
# 生成覆盖率报告
lcov --capture --directory . --output-file coverage.info
genhtml coverage.info --output-directory coverage_report

10.5 CI/CD集成

GitHub Actions示例:

# .github/workflows/build.yml
name: Build

on: [push, pull_request]

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        build_type: [Debug, Release]
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Install vcpkg
      run: |
        git clone https://github.com/Microsoft/vcpkg.git
        ./vcpkg/bootstrap-vcpkg.sh
    
    - name: Configure CMake
      run: |
        cmake -B build -S . \
          -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
          -DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake
    
    - name: Build
      run: cmake --build build --config ${{ matrix.build_type }}
    
    - name: Test
      run: ctest --test-dir build -C ${{ matrix.build_type }} --output-on-failure

📚 推荐学习资源

书籍

  1. 《CMake Cookbook》 - 实用CMake技巧
  2. 《Effective Modern C++》 - C++11/14/17最佳实践
  3. 《API Design for C++》 - 库接口设计

在线资源

  1. CMake官方文档: https://cmake.org/documentation/
  2. vcpkg文档: https://vcpkg.io/
  3. Modern CMake: https://cliutils.gitlab.io/modern-cmake/
  4. C++ Core Guidelines: https://isocpp.github.io/CppCoreGuidelines/

开源项目参考

  1. OpenCV - 大型C++项目组织
  2. Eigen - header-only库设计
  3. LLVM - 模块化架构

✅ 检查清单

在开始新项目时,确保完成以下步骤:

项目初始化

  • 创建Git仓库
  • 设置.gitignore
  • 编写README.md
  • 选择开源许可证(如果适用)

构建系统

  • 创建主CMakeLists.txt
  • 设置C++标准(推荐C++17)
  • 配置编译选项
  • 设置输出目录

依赖管理

  • 安装vcpkg或选择其他方案
  • 创建vcpkg.json
  • 配置CMake工具链文件

模块结构

  • 创建modules目录
  • 创建common模块
  • 为每个模块创建CMakeLists.txt
  • 设置正确的命名空间

代码规范

  • 创建.clang-format
  • 统一命名规范
  • 编写代码注释
  • 准备Doxygen配置(可选)

测试

  • 选择测试框架(Catch2/GoogleTest)
  • 为每个模块添加测试目录
  • 编写基础测试用例
  • 配置CTest

文档

  • 编写架构文档
  • 编写使用说明
  • 添加示例代码
  • 准备API文档

🎯 总结

核心要点

  1. 从简单开始 - 不要一开始就追求完美,逐步迭代
  2. 保持模块独立 - 每个模块应该可以独立编译和测试
  3. 使用现代CMake - target-based而非directory-based
  4. 依赖管理自动化 - 使用vcpkg等工具
  5. 编写测试 - 至少为核心功能编写单元测试
  6. 持续重构 - 随着项目发展调整架构

下一步行动

  1. 实践 - 按照本教程创建一个简单项目
  2. 阅读优秀项目 - 学习OpenCV、Eigen等项目的组织方式
  3. 逐步完善 - 根据需求添加CI/CD、文档生成等功能
  4. 寻求反馈 - 与导师或同学讨论项目架构

📝 版本历史

  • v1.0.0 (2024-11-13) - 初始版本,涵盖基础模块化架构

🤝 反馈与改进

如果你发现本教程有任何问题或改进建议,欢迎:

  • 提交Issue
  • 发起Pull Request
  • 联系作者

祝你的研究项目顺利!🚀

记住:好的架构是逐步演进的,不是一蹴而就的。从简单开始,持续改进!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐