第 18 章:最佳实践
第 18 章:最佳实践
18.1 现代 CMake 原则
18.1.1 以目标为中心
# ❌ 旧风格:全局设置
include_directories(include/)
add_definitions(-DDEBUG)
link_libraries(mylib)
# ✅ 现代风格:目标级别设置
target_include_directories(myapp PRIVATE include/)
target_compile_definitions(myapp PRIVATE DEBUG)
target_link_libraries(myapp PRIVATE mylib)
18.1.2 使用 IMPORTED 目标
# ❌ 变量方式
find_package(OpenSSL)
include_directories(${OPENSSL_INCLUDE_DIR})
link_libraries(${OPENSSL_LIBRARIES})
# ✅ 导入目标方式
find_package(OpenSSL REQUIRED)
target_link_libraries(myapp PRIVATE OpenSSL::SSL)
18.1.3 最小权限原则
# ✅ 仅自己使用
target_link_libraries(mylib PRIVATE fmt::fmt)
# ✅ 需要传递给使用者
target_link_libraries(mylib PUBLIC OpenSSL::SSL)
# ✅ 仅使用者需要(纯头文件库)
target_link_libraries(mylib INTERFACE range-v3::range-v3)
18.1.4 现代 CMake 规则对照表
| 场景 | 旧风格(避免) | 现代风格(推荐) |
|---|---|---|
| 包含目录 | include_directories() | target_include_directories() |
| 编译定义 | add_definitions() | target_compile_definitions() |
| 编译选项 | add_compile_options() | target_compile_options() |
| 链接库 | link_libraries() | target_link_libraries() |
| C++ 标准 | CMAKE_CXX_STANDARD | target_compile_features() |
| 查找库 | 变量 ${XXX_LIBRARIES} | 目标 XXX::XXX |
18.2 项目结构
18.2.1 推荐目录结构
myproject/
├── CMakeLists.txt # 顶层构建文件
├── CMakePresets.json # 预设配置
├── cmake/ # CMake 模块
│ ├── FindMyLib.cmake
│ ├── CompilerWarnings.cmake
│ ├── Sanitizers.cmake
│ ├── MyProjectConfig.cmake.in
│ └── Version.cmake
├── include/ # 公共头文件
│ └── myproject/
│ ├── core.h
│ ├── utils.h
│ └── version.h
├── src/ # 源代码
│ ├── CMakeLists.txt
│ ├── core.cpp
│ └── utils.cpp
├── tests/ # 测试
│ ├── CMakeLists.txt
│ ├── test_core.cpp
│ └── test_utils.cpp
├── examples/ # 示例
│ ├── CMakeLists.txt
│ └── example1.cpp
├── docs/ # 文档
│ ├── CMakeLists.txt
│ └── Doxyfile.in
├── third_party/ # 第三方库(可选)
│ └── CMakeLists.txt
└── scripts/ # 脚本
├── build.sh
└── ci.sh
18.2.2 顶层 CMakeLists.txt 模板
cmake_minimum_required(VERSION 3.16...3.28)
project(MyProject
VERSION 1.0.0
DESCRIPTION "项目描述"
LANGUAGES CXX C
)
# 仅在主项目中启用(非被依赖时)
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
# 默认构建类型
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "构建类型" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
Debug Release RelWithDebInfo MinSizeRel)
endif()
# 测试
option(BUILD_TESTING "构建测试" ON)
include(CTest)
endif()
# 模块路径
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
# 编译选项
add_library(project_warnings INTERFACE)
include(CompilerWarnings)
add_library(project_options INTERFACE)
target_compile_features(project_options INTERFACE cxx_std_17)
# 子目录
add_subdirectory(src)
if(BUILD_TESTING)
add_subdirectory(tests)
endif()
option(BUILD_EXAMPLES "构建示例" OFF)
if(BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
# 安装和打包
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
include(CPack)
18.2.3 src/CMakeLists.txt
add_library(myproject_core
core.cpp
utils.cpp
)
target_include_directories(myproject_core
PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
target_link_libraries(myproject_core
PUBLIC project_options
PRIVATE project_warnings
)
# 设置输出名
set_target_properties(myproject_core PROPERTIES
OUTPUT_NAME "myproject"
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
POSITION_INDEPENDENT_CODE ON
)
# 别名
add_library(MyProject::Core ALIAS myproject_core)
# 安装
install(TARGETS myproject_core
EXPORT MyProjectTargets
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
18.3 代码组织最佳实践
18.3.1 编译器警告配置
# cmake/CompilerWarnings.cmake
function(myproject_set_warnings target)
option(WARNINGS_AS_ERRORS "将警告视为错误" OFF)
set(MSVC_WARNINGS /W4 /WX /wd4251 /wd4275)
set(CLANG_WARNINGS -Wall -Wextra -Wpedantic -Wshadow -Wconversion)
set(GCC_WARNINGS ${CLANG_WARNINGS} -Wmisleading-indentation -Wduplicated-cond)
if(WARNINGS_AS_ERRORS)
list(APPEND CLANG_WARNINGS -Werror)
list(APPEND GCC_WARNINGS -Werror)
list(APPEND MSVC_WARNINGS /WX)
endif()
target_compile_options(${target} INTERFACE
$<$<CXX_COMPILER_ID:MSVC>:${MSVC_WARNINGS}>
$<$<CXX_COMPILER_ID:Clang>:${CLANG_WARNINGS}>
$<$<CXX_COMPILER_ID:GNU>:${GCC_WARNINGS}>
)
endfunction()
myproject_set_warnings(project_warnings)
18.3.2 Sanitizer 配置
# cmake/Sanitizers.cmake
option(ENABLE_ADDRESS_SANITIZER "启用 ASan" OFF)
option(ENABLE_THREAD_SANITIZER "启用 TSan" OFF)
option(ENABLE_UNDEFINED_SANITIZER "启用 UBSan" OFF)
function(myproject_enable_sanitizers target)
if(ENABLE_ADDRESS_SANITIZER)
target_compile_options(${target} INTERFACE
-fsanitize=address -fno-omit-frame-pointer
)
target_link_options(${target} INTERFACE -fsanitize=address)
endif()
if(ENABLE_THREAD_SANITIZER)
if(ENABLE_ADDRESS_SANITIZER)
message(FATAL_ERROR "ASan 和 TSan 不能同时启用")
endif()
target_compile_options(${target} INTERFACE -fsanitize=thread)
target_link_options(${target} INTERFACE -fsanitize=thread)
endif()
if(ENABLE_UNDEFINED_SANITIZER)
target_compile_options(${target} INTERFACE -fsanitize=undefined)
target_link_options(${target} INTERFACE -fsanitize=undefined)
endif()
endfunction()
add_library(sanitizers INTERFACE)
myproject_enable_sanitizers(sanitizers)
18.3.3 版本管理
# cmake/Version.cmake
# 从 git 获取版本信息
function(myproject_get_version)
find_package(Git QUIET)
if(GIT_FOUND)
execute_process(
COMMAND ${GIT_EXECUTABLE} describe --tags --always
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
OUTPUT_VARIABLE GIT_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
OUTPUT_VARIABLE GIT_HASH
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
endif()
set(PROJECT_GIT_VERSION "${GIT_VERSION}" PARENT_SCOPE)
set(PROJECT_GIT_HASH "${GIT_HASH}" PARENT_SCOPE)
endfunction()
myproject_get_version()
message(STATUS "Git 版本: ${PROJECT_GIT_VERSION}")
message(STATUS "Git 哈希: ${PROJECT_GIT_HASH}")
// version.h.in
#pragma once
#define MYPROJECT_VERSION "@PROJECT_VERSION@"
#define MYPROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define MYPROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define MYPROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@
#define MYPROJECT_GIT_VERSION "@PROJECT_GIT_VERSION@"
#define MYPROJECT_GIT_HASH "@PROJECT_GIT_HASH@"
18.4 性能优化
18.4.1 减少配置时间
# 1. 避免 file(GLOB)
# ❌
file(GLOB_RECURSE SOURCES "src/*.cpp")
# ✅
set(SOURCES src/a.cpp src/b.cpp src/c.cpp)
# 2. 减少 try_compile 调用
# 3. 使用预编译头
target_precompile_headers(mylib PRIVATE
<vector>
<string>
<memory>
<algorithm>
)
# 4. 并行配置
# 无直接支持,但可以优化 CMakeLists.txt
18.4.2 减少构建时间
# 1. Unity Build(合并编译单元)
set_target_properties(mylib PROPERTIES UNITY_BUILD ON)
# 2. 预编译头
target_precompile_headers(mylib PRIVATE "pch.h")
# 3. ccache 集成
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
endif()
# 4. 对象库避免重复编译
add_library(backend_objects OBJECT src/backend.cpp)
add_library(backend_static STATIC $<TARGET_OBJECTS:backend_objects>)
add_library(backend_shared SHARED $<TARGET_OBJECTS:backend_objects>)
# 5. 排除非必要目标
add_subdirectory(tests EXCLUDE_FROM_ALL)
18.4.3 减少安装体积
# 1. 去除调试信息
install(CODE "execute_process(COMMAND ${CMAKE_STRIP} \$<TARGET_FILE:myapp>)")
# 2. LTO(链接时优化)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
# 3. 按组件安装
install(TARGETS myapp
RUNTIME DESTINATION bin COMPONENT Runtime
)
install(TARGETS mylib
ARCHIVE DESTINATION lib COMPONENT Development
LIBRARY DESTINATION lib COMPONENT Runtime
)
# 4. 分离调试符号
set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
18.5 可维护性
18.5.1 清晰的依赖关系
# 每个模块明确声明自己的依赖
add_library(core src/core.cpp)
target_link_libraries(core PUBLIC base)
add_library(network src/network.cpp)
target_link_libraries(network PUBLIC core OpenSSL::SSL)
add_library(database src/database.cpp)
target_link_libraries(database PUBLIC core PostgreSQL::PostgreSQL)
# 应用程序只需链接需要的模块
add_executable(app main.cpp)
target_link_libraries(app PRIVATE network database)
18.5.2 避免全局状态
# ❌ 全局设置影响所有目标
set(CMAKE_CXX_FLAGS "-Wall -Wextra")
add_definitions(-DDEBUG)
include_directories(include/)
# ✅ 使用接口库封装配置
add_library(project_defaults INTERFACE)
target_compile_options(project_defaults INTERFACE
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wall -Wextra>
$<$<CXX_COMPILER_ID:MSVC>:/W4>
)
target_include_directories(project_defaults INTERFACE include/)
target_compile_features(project_defaults INTERFACE cxx_std_17)
# 使用时
target_link_libraries(mylib PUBLIC project_defaults)
18.5.3 文档化 CMakeLists.txt
# 模块说明
#[=======================================================================[.rst:
MyModule
--------
提供项目的通用配置。
功能
^^^^
- 设置编译器警告
- 配置 Sanitizer
- 定义版本信息
使用方法
^^^^^^^^
.. code-block:: cmake
include(MyModule)
target_link_libraries(myapp PRIVATE project_defaults)
#]=======================================================================]
18.5.4 版本控制
# .gitignore
build/
build-*/
.cache/
CMakeUserPresets.json
*.user
compile_commands.json
// .vscode/settings.json
{
"cmake.buildDirectory": "${workspaceFolder}/build",
"cmake.configureOnOpen": true,
"cmake.configureSettings": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
}
18.6 测试最佳实践
18.6.1 测试组织
# tests/CMakeLists.txt
enable_testing()
# 测试目标
add_executable(test_core test_core.cpp)
target_link_libraries(test_core PRIVATE
MyProject::Core
GTest::gtest_main
)
# 自动发现测试
include(GoogleTest)
gtest_discover_tests(test_core
PROPERTIES
LABELS "unit"
TIMEOUT 30
)
18.6.2 测试分类
# 按类型组织测试
add_subdirectory(unit) # 单元测试
add_subdirectory(integration) # 集成测试
add_subdirectory(performance) # 性能测试
# CTest 过滤
# ctest -L unit # 运行单元测试
# ctest -L integration # 运行集成测试
# ctest -LE "slow|performance" # 排除慢测试
18.7 安全最佳实践
18.7.1 编译安全选项
# 安全编译选项
target_compile_options(myapp PRIVATE
# 堆栈保护
-fstack-protector-strong
# 格式化字符串保护
-Wformat -Wformat-security -Werror=format-security
# 位置无关代码
-fPIC
# 立即绑定
-Wl,-z,now
# 不可执行栈
-Wl,-z,noexecstack
)
# 安全定义
target_compile_definitions(myapp PRIVATE
_FORTIFY_SOURCE=2
)
18.7.2 依赖安全
# 锁定依赖版本
FetchContent_Declare(fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1 # 明确版本
GIT_SHALLOW TRUE
)
# 验证下载哈希
FetchContent_Declare(mylib
URL https://example.com/mylib-2.0.tar.gz
URL_HASH SHA256=abc123...
)
18.8 跨平台最佳实践
18.8.1 平台抽象
# 平台特定源文件
add_library(mylib
src/common.cpp
$<$<PLATFORM_ID:Linux>:src/linux.cpp>
$<$<PLATFORM_ID:Windows>:src/windows.cpp>
$<$<PLATFORM_ID:Darwin>:src/macos.cpp>
)
# 平台特定链接
target_link_libraries(mylib PRIVATE
$<$<PLATFORM_ID:Linux>:rt;dl>
$<$<PLATFORM_ID:Windows>:ws2_32;bcrypt>
$<$<PLATFORM_ID:Darwin>:"-framework CoreFoundation">
)
18.8.2 编译器抽象
# 编译器特定选项
target_compile_options(mylib PRIVATE
$<$<CXX_COMPILER_ID:GNU>:
-Wall -Wextra -Wpedantic -Wshadow -Wconversion
$<$<VERSION_GREATER_EQUAL:${CMAKE_CXX_COMPILER_VERSION},10>:-Wconversion>
>
$<$<CXX_COMPILER_ID:Clang>:
-Wall -Wextra -Wpedantic -Wshadow -Wconversion
>
$<$<CXX_COMPILER_ID:MSVC>:
/W4 /WX /wd4251 /wd4275 /wd4996
>
)
18.9 CMakePresets.json 标准配置
{
"version": 6,
"cmakeMinimumRequired": { "major": 3, "minor": 21 },
"configurePresets": [
{
"name": "base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"BUILD_TESTING": "ON"
}
},
{
"name": "debug",
"displayName": "Debug",
"inherits": "base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "release",
"displayName": "Release",
"inherits": "base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "ci",
"displayName": "CI",
"inherits": "release",
"cacheVariables": {
"WARNINGS_AS_ERRORS": "ON"
},
"condition": {
"type": "equals",
"lhs": "$env{CI}",
"rhs": "true"
}
}
],
"buildPresets": [
{ "name": "debug", "configurePreset": "debug" },
{ "name": "release", "configurePreset": "release" }
],
"testPresets": [
{
"name": "quick",
"configurePreset": "debug",
"filter": { "include": { "label": "fast" } }
},
{
"name": "full",
"configurePreset": "debug",
"output": { "outputOnFailure": true }
}
]
}
18.10 最佳实践清单
✅ 项目设置
- 使用
cmake_minimum_required(VERSION 3.16...3.28) - 使用 out-of-source 构建
- 设置
CMAKE_EXPORT_COMPILE_COMMANDS - 提供
CMakePresets.json - 将
CMakeUserPresets.json加入.gitignore
✅ 目标管理
- 以目标为中心设置属性
- 正确使用 PRIVATE/PUBLIC/INTERFACE
- 使用命名空间别名(
MyProject::Core) - 为库设置 SOVERSION
✅ 依赖管理
- 优先使用 find_package 的 IMPORTED 目标
- FetchContent 使用明确的 tag/hash
- 考虑 FIND_PACKAGE_ARGS 的优先级
✅ 编译配置
- 使用
target_compile_features()设置标准 - 设置
CXX_EXTENSIONS OFF - 配置编译器警告
- 支持 Sanitizer
✅ 安装与打包
- 使用
GNUInstallDirs - 生成并安装包配置文件
- 设置 RPATH
- 提供 CPack 配置
✅ 测试
- 集成测试框架
- 使用
gtest_discover_tests() - 设置测试标签
- 配置覆盖率
✅ 可维护性
- 清晰的目录结构
- 模块化的 CMakeLists.txt
- 避免全局状态
- 文档化复杂的 CMake 逻辑
18.11 扩展阅读
- Modern CMake — 现代 CMake 教程
- Effective Modern CMake — 最佳实践总结
- Professional CMake: A Practical Guide — 最全面的 CMake 参考书
- CMake 文档 — 官方文档
- CMake Examples — 实用示例集
上一章:第 17 章 — 问题排查 | 返回目录:目录