C++模块化项目构建入门教
适用场景: 研究生阶段的个人/小团队项目,多模块C++开发
难度级别: 中级
预计阅读时间: 30分钟
最后更新: 2024年11月
📋 目录
- 为什么需要模块化
- 项目架构选择
- 目录结构设计
- CMake配置详解
- 依赖管理方案
- 模块开发流程
- 代码组织规范
- 实战案例
- 常见问题
- 进阶主题
1. 为什么需要模块化
问题场景
作为研究生,你可能面临这些挑战:
❌ 所有代码堆在一个文件夹
❌ 不同研究方向的代码混在一起
❌ 修改一处代码影响整个项目
❌ 无法独立测试某个功能
❌ 代码复用困难
模块化优势
✅ 清晰的代码组织结构
✅ 独立开发、测试、维护
✅ 代码复用性强
✅ 便于版本管理
✅ 支持选择性编译
✅ 易于扩展新功能
适用场景判断
适合模块化的项目:
- 包含3个以上独立功能模块
- 需要长期维护和扩展
- 有代码复用需求
- 涉及多个研究方向
不需要模块化的项目:
- 一次性实验脚本
- 功能单一的小工具
- 快速原型验证
2. 项目架构选择
方案对比
| 架构类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 单体仓库 | 单文件夹项目 | 简单直接 | 难以扩展 |
| 多仓库 | 独立子项目 | 完全解耦 | 管理复杂 |
| Monorepo | 统一管理多模块 | 易于共享代码 版本同步 | 需要学习成本 |
推荐方案:Monorepo
理由:
- 个人/小团队最优解 - 一个仓库管理所有模块
- 代码共享方便 - 公共代码放在common模块
- 依赖管理简单 - 统一的第三方库版本
- 重构友好 - 跨模块重构容易
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.0DESCRIPTION "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(HEADERSinclude/uav/trajectory/dubins_path.hinclude/uav/trajectory/g2cbs_smoother.hinclude/uav/trajectory/time_interpolator.h
)set(SOURCESsrc/dubins_path.cppsrc/g2cbs_smoother.cppsrc/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::Eigenuav::commonPRIVATE# 私有依赖(仅内部使用)# 例如:某些实现细节的库
)# ============================================
# 编译选项
# ============================================# 针对不同编译器的警告设置
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} PROPERTIESFOLDER "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}TargetsLIBRARY DESTINATION libARCHIVE DESTINATION libRUNTIME DESTINATION binINCLUDES DESTINATION include
)# 安装头文件
install(DIRECTORY include/DESTINATION includeFILES_MATCHING PATTERN "*.h"
)# 导出目标(供其他项目使用)
install(EXPORT ${PROJECT_NAME}TargetsFILE ${PROJECT_NAME}Targets.cmakeNAMESPACE 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}PRIVATEuav::commonuav::trajectory
)# IDE文件夹组织
set_target_properties(${PROJECT_NAME} PROPERTIESFOLDER "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.cpptest_g2cbs.cpp
)# 链接测试框架和被测模块
target_link_libraries(${PROJECT_NAME}PRIVATEuav::trajectoryCatch2::Catch2WithMain # 或 Catch2::Catch2
)# 添加测试
include(CTest)
include(Catch) # 如果使用Catch2catch_discover_tests(${PROJECT_NAME})# 或者手动添加测试
# add_test(NAME TrajectoryTests COMMAND ${PROJECT_NAME})# IDE文件夹
set_target_properties(${PROJECT_NAME} PROPERTIESFOLDER "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-windowscmake --build build --config Release# Linux/Mac
cmake -B build -S . \-DCMAKE_TOOLCHAIN_FILE=~/dev/vcpkg/scripts/buildsystems/vcpkg.cmakecmake --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_targetPRIVATEEigen3::Eigenfmt::fmtspdlog::spdlognlohmann_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 * xx_ = F_ * x_;// 协方差预测: P = F * P * F^T + QP_ = F_ * P_ * F_.transpose() + Q_;
}void KalmanFilter::update(const Eigen::VectorXd& measurement) {// 创新(残差): y = z - H * xEigen::VectorXd y = measurement - H_ * x_;// 创新协方差: S = H * P * H^T + REigen::MatrixXd S = H_ * P_ * H_.transpose() + R_;// 卡尔曼增益: K = P * H^T * S^(-1)Eigen::MatrixXd K = P_ * H_.transpose() * S.inverse();// 状态更新: x = x + K * yx_ = x_ + K * y;// 协方差更新: P = (I - K * H) * PEigen::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(HEADERSinclude/uav/sensor_fusion/kalman_filter.h
)set(SOURCESsrc/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}PUBLICEigen3::Eigenuav::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_pathsrc/line_path.cpp
)target_include_directories(uav_pathPUBLIC$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
)target_link_libraries(uav_pathPUBLICEigen3::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_demomain.cpp
)target_link_libraries(uav_demoPRIVATEuav_pathuav_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_targetPUBLIC${CMAKE_SOURCE_DIR}/modules/trajectory/include
)
Q2: 链接错误
问题:
undefined reference to `uav::trajectory::DubinsPath::compute()`
解决方案:
# 确保链接了正确的库
target_link_libraries(my_appPRIVATEuav::trajectory # 使用别名
)
Q3: Eigen相关错误
问题:
error: no member named 'Vector3d' in namespace 'Eigen'
解决方案:
# 确保找到并链接了Eigen
find_package(Eigen3 3.3 REQUIRED NO_MODULE)target_link_libraries(my_targetPUBLICEigen3::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项目
解决方案:
- 确保安装了"使用C++的桌面开发"工作负载
- 在Visual Studio中:文件 → 打开 → CMake → 选择CMakeLists.txt
- 配置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
解决方案:
- 重新设计模块接口,消除循环依赖
- 提取公共部分到新模块
- 使用依赖倒置原则(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: Buildon: [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 vcpkgrun: |git clone https://github.com/Microsoft/vcpkg.git./vcpkg/bootstrap-vcpkg.sh- name: Configure CMakerun: |cmake -B build -S . \-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \-DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake- name: Buildrun: cmake --build build --config ${{ matrix.build_type }}- name: Testrun: ctest --test-dir build -C ${{ matrix.build_type }} --output-on-failure
📚 推荐学习资源
书籍
- 《CMake Cookbook》 - 实用CMake技巧
- 《Effective Modern C++》 - C++11/14/17最佳实践
- 《API Design for C++》 - 库接口设计
在线资源
- CMake官方文档: https://cmake.org/documentation/
- vcpkg文档: https://vcpkg.io/
- Modern CMake: https://cliutils.gitlab.io/modern-cmake/
- C++ Core Guidelines: https://isocpp.github.io/CppCoreGuidelines/
开源项目参考
- OpenCV - 大型C++项目组织
- Eigen - header-only库设计
- 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文档
🎯 总结
核心要点
- 从简单开始 - 不要一开始就追求完美,逐步迭代
- 保持模块独立 - 每个模块应该可以独立编译和测试
- 使用现代CMake - target-based而非directory-based
- 依赖管理自动化 - 使用vcpkg等工具
- 编写测试 - 至少为核心功能编写单元测试
- 持续重构 - 随着项目发展调整架构
下一步行动
- 实践 - 按照本教程创建一个简单项目
- 阅读优秀项目 - 学习OpenCV、Eigen等项目的组织方式
- 逐步完善 - 根据需求添加CI/CD、文档生成等功能
- 寻求反馈 - 与导师或同学讨论项目架构
📝 版本历史
- v1.0.0 (2024-11-13) - 初始版本,涵盖基础模块化架构
🤝 反馈与改进
如果你发现本教程有任何问题或改进建议,欢迎:
- 提交Issue
- 发起Pull Request
- 联系作者
祝你的研究项目顺利!🚀
记住:好的架构是逐步演进的,不是一蹴而就的。从简单开始,持续改进!
