必须用private时,是因头文件、宏或链接库仅服务当前target自身且下游绝不可感知,如内部实现头文件、单元测试mock库或编译器调试选项。

什么时候必须用 PRIVATE
当你加的头文件、宏或链接库只服务于当前 target 自身,且下游绝对不该感知时,就该用 PRIVATE。典型场景是:内部实现头文件(比如 detail/ 下的私有类)、仅用于单元测试的 mock 库、或者编译器特定的调试选项(如 -fsanitize=address)。
常见错误现象:target_include_directories(mylib PRIVATE include/) 后,下游链接 mylib 的可执行文件却找不到 mylib.h —— 这不是 bug,是预期行为。因为 PRIVATE 不传播头路径,下游根本看不到你暴露的 public 接口。
- 源码级封装:用
target_sources(mylib PRIVATE src/detail_impl.cpp)防止被下游误包含 - 避免污染:
target_compile_definitions(mylib PRIVATE INTERNAL_ONLY)确保宏不会意外影响下游编译 - 性能考量:
PRIVATE属性不参与依赖图传递,CMake 解析更快,尤其在大型项目中
PUBLIC 适用于“我用,且我的用户也得用”
PUBLIC 是最常被误用也最容易引发隐式耦合的选项。它表示:当前 target 编译需要这些配置,同时所有链接它的 target 也必须继承这些配置。换句话说,你在定义一个“契约”——别人用你,就得按你的规则来。
典型使用场景:add_library(mylib STATIC mylib.cpp) 暴露了 mylib.h,而这个头文件依赖 third_party/vec3.h;那么必须写 target_include_directories(mylib PUBLIC $<include> $<build_interface:>)</build_interface:></include>,再配 target_include_directories(mylib PUBLIC ${THIRD_PARTY_INCLUDE}),否则下游包含 mylib.h 就会报错。
CMake 4.3.2 Windows x86_64 历史版本安装包,适合旧项目兼容、构建环境回退、CMakeLists.txt 迁移验证、Visual Studio/Ninja/Makefile 生成器测试和 C/C++ 项目维护。
- 头文件 + 宏必须同步:如果
mylib.h中用了MYLIB_EXPORT宏,那target_compile_definitions(mylib PUBLIC MYLIB_EXPORT)和target_include_directories(mylib PUBLIC ...)得成对出现 - 链接库传播要谨慎:用
target_link_libraries(mylib PUBLIC zlib)意味着任何链接mylib的目标,都会自动链接zlib—— 如果下游本已链接不同版本的zlib,就会冲突 - STATIC 库用
PUBLIC时,下游即使只声明target_link_libraries(app PRIVATE mylib),仍会继承mylib的PUBLIC属性(这是 CMake 规则,不是 bug)
INTERFACE 库不是“库”,是“说明书”
真正理解 INTERFACE 的关键,是忘掉“库”字。它不生成任何二进制文件,只负责广播一组构建约束。当你看到 add_library(log_config INTERFACE),它实际等价于:“任何链接我的目标,请务必加 -DUSE_SPDLOG=1,并把 spdlog/include 加进自己的 -I 路径,再链接 spdlog::spdlog”。
容易踩的坑:target_link_libraries(myapp PRIVATE log_config) 是错的 —— INTERFACE 库本身不提供符号,PRIVATE 会让这些约束完全丢失;正确写法是 target_link_libraries(myapp PUBLIC log_config) 或 INTERFACE(取决于你是否希望 myapp 的下游也继承这些约束)。
- 纯头文件库(header-only)必须用
INTERFACE:比如add_library(fmt INTERFACE),然后target_include_directories(fmt INTERFACE ${FMT_INCLUDE_DIR}) -
INTERFACE不能和源文件共存:add_library(x INTERFACE x.cpp)会报错,CMake 明确禁止 - 传播链断裂点:如果 A → B → C,B 用
PRIVATE链接 A(A 是INTERFACE),那么 C 就收不到 A 的任何约束 —— 这是设计使然,不是 bug
怎么一眼判断该选哪个
别从语义猜,直接问三个问题:
- 这个配置项是否出现在当前 target 的源码里?(比如
#include "xxx.h"或用了XXX_MACRO)→ 是 → 至少得PUBLIC或PRIVATE - 下游 target 的源码里,是否也必须能
#include这个头、或依赖这个宏?→ 是 → 必须PUBLIC(或通过INTERFACE库间接提供) - 当前 target 自己压根不碰这个东西,只是替下游“带话”?→ 是 → 只能用
INTERFACE,且必须搭配target_link_libraries(... PUBLIC/INTERFACE xxx)才能生效
最易忽略的一点:CMake 不检查逻辑合理性。你完全可以写 target_compile_definitions(mylib INTERFACE FOO=1),但若没人链接 mylib,这行就彻底无效;也可以写 target_include_directories(mylib PUBLIC /dev/null),CMake 也不会报错 —— 它只管传播,不管有没有用。










