C++模块化项目构建入门教
·
适用场景: 研究生阶段的个人/小团队项目,多模块C++开发
难度级别: 中级
预计阅读时间: 30分钟
最后更新: 2024年11月
📋 目录
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.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项目
解决方案:
- 确保安装了"使用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: 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
📚 推荐学习资源
书籍
- 《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
- 联系作者
祝你的研究项目顺利!🚀
记住:好的架构是逐步演进的,不是一蹴而就的。从简单开始,持续改进!
更多推荐


所有评论(0)