我以前对项目模板的理解比较简单:建好 src、include、tests,再放一个能跑的 CMakeLists.txt,就算完成。后来真正维护几个 C++ 项目之后,才发现模板的价值不在“看起来像工程”,而在它能不能降低后续修改的心理成本。
一个好的 CMake 模板,应该让你在新项目开始时少做无意义选择,同时保留足够清晰的扩展空间。
模板首先要定义边界
最重要的边界,是库代码和可执行入口之间的边界。
我更倾向于把核心逻辑放进一个明确的 library target,再让命令行程序、测试程序、示例程序去链接它。这样做有几个好处:
- 核心逻辑可以被测试直接调用;
- CLI 入口不会把业务代码和参数解析缠在一起;
- 后续如果要加 GUI、服务端或 Python 绑定,不需要重写底层结构;
- 构建目标之间的依赖关系更清楚。
CMake 里最容易失控的地方,就是所有东西都写进一个全局变量和一个巨大 target。刚开始很快,后面每加一个依赖都像在碰运气。
依赖入口要克制
模板不应该预装一堆“可能会用到”的库。
我更喜欢只保留一个清晰的依赖接入位置,比如 cmake/Dependencies.cmake,然后在 README 里写明:项目依赖统一从这里进入,不要散落到每个子目录。
这不是形式主义。依赖一旦散开,后续排查构建问题会非常痛苦:谁引入了 OpenCV,谁设置了编译宏,谁改变了 warning 级别,都会变得不透明。
模板的克制感很重要。默认越简单,项目越容易长期维护。
选项要服务真实工作流
我会给模板保留少量构建选项,例如:
option(PROJECT_BUILD_TESTS "Build tests" ON)
option(PROJECT_BUILD_EXAMPLES "Build examples" ON)
option(PROJECT_ENABLE_WARNINGS "Enable warnings" ON)
这些选项的目标不是显得专业,而是服务真实场景:
- 本地开发时需要测试;
- CI 里需要稳定检查;
- 打包或嵌入其他项目时可能只需要 library;
- 示例程序可以帮助验证接口,但不应该强制参与所有构建。
好的模板应该让这些选择变得显式,而不是靠改文件来切换。
测试入口必须早一点出现
很多项目不是没有测试能力,而是最开始没有留入口。等代码已经长大,再补测试结构,成本就会明显上升。
所以即使第一版只有一个 smoke test,我也愿意把 tests/ 和 ctest 跑通。这个动作会提醒自己:项目不是只能编译,还应该能被验证。
对于医学图像或图形工具类项目,测试不一定全部是复杂算法结果。最开始可以先覆盖这些内容:
- 文件能否正确读取;
- 参数解析是否稳定;
- 核心函数对边界输入是否有合理行为;
- 一段最小流程能否跑完;
- 输出目录和日志是否按预期生成。
这些测试不华丽,但能防止很多低级回退。
README 也是模板的一部分
模板不是只给机器看的,README 也不是装饰。 一个可用的工程模板,应该在 README 里回答最基本的几个问题:
- 如何配置项目?
- 如何构建?
- 如何运行测试?
- 如何添加新的源文件?
- 项目约定哪些目录职责?
如果这些问题没有写清楚,那么模板只是把文件摆好了,还没有真正降低使用门槛。
最后
一个 CMake 项目模板真正有用的地方,是让项目从第一天开始就有边界、有入口、有验证路径。它不需要复杂,但需要稳定。
我现在判断模板好不好,不看它塞了多少功能,而看它能不能让我在三个月后重新打开项目时,仍然知道应该从哪里改、从哪里测、从哪里接新依赖。
模板的专业感,最终体现在长期维护时的从容感。