推荐选3.20:C++17默认支持、find_package(CONFIG)更可靠、target_link_libraries传递接口属性无需INTERFACE、兼容MSVC /permissive-与C++20 modules、FetchContent支持EXCLUDE_FROM_ALL。

cmake_minimum_required 版本选 3.14 还是 3.20?
选 3.14 是底线,但实际推荐直接写 3.20。C++17 默认支持、find_package(... CONFIG REQUIRED) 行为更可靠、target_link_libraries() 传递接口属性不再需要手动加 INTERFACE —— 这些不是“锦上添花”,而是避免后期改一堆 target 属性的省心前提。
常见错误:用 3.10 写了 add_library(mylib INTERFACE),结果在链接时发现 target_include_directories(mylib INTERFACE ...) 的路径没透传给下游,最后靠全局 include_directories() 补救,污染整个作用域。
- 项目含
std::filesystem或需find_package(Threads REQUIRED)→ 至少3.14 - 用
FetchContent_Declare()拉第三方(如 fmt、doctest)→3.14+可用,但3.20+支持EXCLUDE_FROM_ALL避免意外构建 - 团队有 Windows + MSVC 用户 →
3.20开始对/permissive-和 C++20 modules 的 generator expression 支持更稳
add_executable() / add_library() 后立刻 set_property()?
不建议。现代 CMake 的惯用法是「声明即配置」:把源文件、语言标准、可见性、依赖全塞进 add_executable() 或 add_library() 调用里,而不是事后用 set_property() 补。
典型翻车场景:add_library(foo STATIC foo.cpp) 之后再 target_compile_features(foo PRIVATE cxx_std_17),结果单元测试 target 里 include foo 时因编译特征不一致触发 ODR violation;而写成 add_library(foo STATIC foo.cpp) target_compile_features(foo PRIVATE cxx_std_17) 就能被 CMake 自动推导依赖链。
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 用
PRIVATE控制头文件可见范围:只有实现用到的头,就别放PUBLIC -
target_compile_options()中慎用-Wall -Wextra全局开关,不同编译器含义不同;改用target_compile_options(foo PRIVATE $:-Wall>) - 想让库默认启用 RTTI 或异常?直接
target_compile_features(foo PRIVATE cxx_rtti cxx_exceptions),比set(CMAKE_CXX_FLAGS ...)安全
find_package() 找不到 fmt / spdlog 怎么办?
不是路径问题,大概率是没告诉 CMake 去哪找——尤其当你用 FetchContent 或 vcpkg 管理依赖时。
现象:find_package(fmt REQUIRED) 报错 Could not find fmtConfig.cmake,但 ls build/_deps/fmt-src/ 下明明有 fmt-config.cmake。
- 确保在
find_package()前调用list(APPEND CMAKE_PREFIX_PATH "${CMAKE_BINARY_DIR}/_deps/fmt-src") - 用
vcpkg时,必须在 configure 阶段传-DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake,否则CMAKE_FIND_ROOT_PATH不生效 - 自己写的库要被别人
find_package()到?在 install 步骤里加install(EXPORT mylibTargets FILE MyLibConfig.cmake ...),别只装头文件和 so
configure_file() 替换版本号总出错?
因为 @VAR@ 是 CMake 变量替换语法,不是 shell 或预处理器;如果 VERSION 没提前 set(),它就真替换成空字符串,连 warning 都不报。
最常漏的一步:在 configure_file(version.h.in version.h) 前,忘了 project(MyProj VERSION 1.2.3) 或显式 set(PROJECT_VERSION "1.2.3")。结果生成的 version.h 里 #define MYPROJ_VERSION "@PROJECT_VERSION@",编译直接炸。
- 推荐统一用
project()声明版本,后续所有@PROJECT_VERSION@、@PROJECT_VERSION_MAJOR@都自动可用 - 想区分构建类型(dev / release)?用
configure_file(config.h.in config.h @ONLY),然后在.in文件里写#cmakedefine MYPROJ_DEV_MODE,配合option(MYPROJ_DEV_MODE "Enable dev features" OFF) - 不要在
configure_file()里试图执行命令或逻辑判断,那是string(CONFIGURE ...)的事,且极少需要
真正的难点不在语法,而在「谁该负责提供变量」——是顶层 CMakeLists.txt 统一设,还是每个子目录自己 set() 再 parent_scope 传?后者容易失控,前者又难适配多配置构建。多数项目卡在这儿,不是不会写,是没约定清楚 scope 边界。


















