能,OBJECT_LIBRARY通过预编译共享对象文件减少重复编译,但仅在多目标共用相同源码且正确引用时有效;改头文件仍会触发整个object library重编译。

什么是 OBJECT_LIBRARY,它真能减少重编译吗?
能,但只在特定结构下有效——它把源码编译成中间对象文件(.o 或 .obj),不打包成静态库,也不生成符号表,因此不会触发下游目标的全量重链接。关键在于:它让多个可执行目标或库共享同一组预编译对象,避免重复编译相同源码。
常见错误现象:add_library(myobj OBJECT src/a.cpp src/b.cpp) 写对了,但后续 target_sources(exe1 PRIVATE $<TARGET_OBJECTS:myobj>) 漏掉 PRIVATE 或路径拼错,导致对象没被实际引用,白忙一场。
使用场景:
- 多个测试可执行文件共用同一套工具函数和 fixture 类(比如
test_utils.cpp+mock_network.cpp) - 主程序和插件模块共享核心解析逻辑,但插件需独立加载
- 跨平台项目中,把平台无关逻辑抽为 object library,再按需链接进 Windows DLL / Linux SO
target_sources() 引用 object library 时为什么总报 “unknown target”?
因为 $<TARGET_OBJECTS:xxx> 是生成器表达式,只能用在支持该语法的命令里,最典型的是 target_sources() 和 target_link_libraries()(后者仅限 CMake 3.12+ 且仅用于链接时传递依赖,不推荐)。如果写在 add_executable() 的源码列表里,CMake 会直接报错说找不到 target。
立即学习“C++免费学习笔记(深入)”;
正确做法:
- 必须先定义
add_library(myobj OBJECT ...),且名字不能含::或空格 - 引用时严格用
target_sources(myexe PRIVATE $<TARGET_OBJECTS:myobj>),不能写成target_sources(myexe PRIVATE myobj) - 如果 object library 本身依赖其他库(比如用了
fmt::fmt),得用target_link_libraries(myobj PRIVATE fmt::fmt),否则下游链接时报 undefined reference
参数差异:PRIVATE 表示只影响当前目标,不会把 myobj 的头文件路径或编译定义透传给依赖它的其他目标——这点和静态库不同,得手动补 target_include_directories()。
用 OBJECT_LIBRARY 后,修改头文件还会触发重编译吗?
会,而且可能比想象中更频繁。object library 本身不管理头文件依赖,CMake 只根据源文件时间戳决定是否重编译对象;一旦你改了被 myobj 中某个 .cpp 包含的头文件(比如 common.h),整个 myobj 所有对象都会重编译——哪怕只有 1 个 .cpp 实际用到了这个头文件。
性能影响:
- 优势:避免多目标重复编译同一份
.cpp,尤其适合 CI 上并行构建多个测试二进制 - 代价:失去细粒度增量编译能力;若 object library 包含 20 个源文件,改一个头文件就 recompile 20 个对象
- 兼容性:CMake 3.12+ 才完全支持
$<TARGET_OBJECTS:...>在target_sources()中展开;旧版本只能靠file(GLOB ...)手动收集.o,不可靠
建议搭配 set_property(TARGET myobj PROPERTY POSITION_INDEPENDENT_CODE ON),否则在链接成 shared library 时可能报 relocation R_X86_64_32 错误。
为什么有时候加了 OBJECT_LIBRARY 反而编译变慢了?
最常踩的坑是把它当“万能去重工具”滥用:把几十个业务模块的源码全塞进一个大 object library,结果每次改任意一个头文件,所有模块的对象都得重编。CMake 不会自动分析头文件包含图,也不会跳过未受影响的对象。
真正有效的切分方式:
- 按变更频率分组:稳定的基础工具类一组,高频迭代的协议解析一组
- 按头文件作用域隔离:确保
obj_group_a的头文件不会被obj_group_b的源文件 #include - 避免跨 object library 的头文件循环依赖,否则 CMake 可能静默忽略某些依赖声明
还有一个隐蔽问题:IDE(如 CLion、VS)对 OBJECT_LIBRARY 的索引支持弱,跳转定义、重命名可能失效——这不是 CMake 的问题,但会影响日常开发体验。
复杂点在于,它不改变头文件依赖的本质,只是把“编译动作”的粒度从文件级挪到了 object library 级。想靠它解决头文件爆炸问题,得先理清 include 树,否则只是把麻烦延后了。


















