Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

本页目录

项目目录结构、第三方库集成与 CMake 实战 / Project Structure, Third-Party Dependencies, and CMake ​

📅 创建时间:2026-07-13 🏷️ 标签:#C++ #CMake #项目结构 #第三方库 #vcpkg #Conan 📚 前置知识:cpp project fundamentals, windows build outputs, linux build outputs


📋 本章目标 ​

  • 掌握 C++ 项目的标准目录结构布局
  • 理解第三方库集成的四种场景:预编译二进制 vs 源码 vs 包管理器 vs 系统包
  • 彻底搞懂 find_package 的 Module 模式和 Config 模式
  • 掌握 CMake 的核心三件套:add_library / add_executable → target_link_libraries
  • 理解 PUBLIC / PRIVATE / INTERFACE 依赖传递的语义
  • 学会使用 FetchContent 和 ExternalProject 处理源码级第三方库
  • 掌握 RPATH 配置的最佳实践

第1部分:标准 C++ 项目目录结构 ​

1.1 两种经典布局 ​

方案 A:include 和 src 分离(库项目推荐)

my_library/                          # 适合:你要发布一个库给别人用
├── CMakeLists.txt
├── include/                         # 公开API —— 给使用者看的
│   └── my_library/
│       ├── api.h
│       ├── types.h
│       └── version.h
├── src/                             # 内部实现 —— 使用者不需要关心
│   ├── api.cpp
│   ├── internal_impl.cpp
│   └── CMakeLists.txt
├── test/
├── examples/
└── external/                        # 第三方依赖源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14

方案 B:.h 和 .cpp 放在一起(应用程序项目推荐)

my_application/
├── CMakeLists.txt
├── src/
│   ├── main.cpp
│   ├── core/
│   │   ├── engine.h                 # .h 和 .cpp 在同一个目录
│   │   └── engine.cpp
│   ├── ui/
│   │   ├── window.h
│   │   └── window.cpp
│   └── utils/
│       ├── logger.h
│       └── logger.cpp
├── test/
└── external/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌─────────────────────────────────────────────────────────────────────────────┐
│              方案选择建议                                                     │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  方案A(分离式):                                                            │
│  ✅ 适合:你在开发一个库,会被别人 #include                                   │
│  ✅ 优点:公开接口和内部实现物理隔离,使用者一眼看到 API                       │
│  ✅ include/my_library/ 的嵌套结构利于 install 后组织                         │
│                                                                             │
│  方案B(同目录):                                                            │
│  ✅ 适合:应用程序、内部工具                                                  │
│  ✅ 优点:改 .h 时 .cpp 就在旁边,不需要跳目录                                │
│  ✅ 结构扁平,小型项目更直观                                                  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

1.2 install 的概念——你的项目被别人使用时的样子 ​

当你 cmake --install 或 make install 后,文件会被拷贝到系统标准位置:

安装前(你的开发目录)              安装后(系统目录)
─────────────────────────          ────────────────────────
project/                           /usr/local/
├── include/                       ├── include/
│   └── mylib/                     │   └── mylib/
│       └── api.h       ───→      │       └── api.h
├── src/                           │
│   └── impl.cpp                   ├── lib/
└── build/                         │   ├── libmylib.so -> libmylib.so.1
    └── libmylib.so     ───→      │   └── libmylib.so.1 -> libmylib.so.1.0.0
                                   │   └── libmylib.so.1.0.0
                                   └── share/
                                       └── mylib/
                                           └── mylibConfig.cmake  ← CMake 包配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14

这就是为什么 include/ 里要用 mylib/api.h 的嵌套结构——防止安装后头文件名冲突。


第2部分:第三方库集成——四种场景 ​

这是日常开发中最常遇到的工程问题。

场景总览 ​

┌─────────────────────────────────────────────────────────────────────────────┐
│          第三方库集成的四种场景                                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  场景1:甲方给了预编译二进制                                                  │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │ 交付物:.h + .lib + .dll(Windows)                            │         │
│  │        .h + .so(Linux,直接给二进制)                        │         │
│  │        .h + .a(Linux,静态库)                               │         │
│  │                                                               │         │
│  │ 你需要:                                                       │         │
│  │ 1. 把头文件路径加到 include 搜索路径                            │         │
│  │ 2. 把库文件路径加到链接器搜索路径                                │         │
│  │ 3. 告诉 CMake 链接这个库                                       │         │
│  │                                                               │         │
│  │ 优点:不需要编译,直接链接即可                                  │         │
│  │ 痛点:编译器版本不匹配(第05篇深入)                            │         │
│  │       缺少某个平台的版本                                       │         │
│  │       库的 Debug 版本和 Release 版本要分开                     │         │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
│  场景2:甲方只给了源码                                                       │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │ 交付物:一堆 .cpp 和 .h                                        │         │
│  │                                                               │         │
│  │ 你需要:                                                       │         │
│  │ 1. 看它有没有 CMakeLists.txt                                   │         │
│  │    • 有 → FetchContent 或 add_subdirectory                    │         │
│  │    • 没有 → 自己写 CMakeLists.txt 或 ExternalProject           │         │
│  │ 2. 看它有没有第三方依赖                                        │         │
│  │    • 先解决它的依赖,再解决你自己的                             │         │
│  │                                                               │         │
│  │ 优点:可以 Debug 进去看源码、可以修改、编译器选项可控            │         │
│  │ 痛点:编译慢(每次 CI 都要重新编)                              │         │
│  │       依赖传递(它的依赖你也得处理)                            │         │
│  │       源码里可能有硬编码路径、平台特定代码等坑                   │         │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
│  场景3:用包管理器                                                           │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │ • vcpkg(Microsoft 出品,Windows 上最方便)                     │         │
│  │ • Conan(跨平台,C/C++ 专用,企业用户多)                       │         │
│  │ • 系统包管理器(apt install libxxx-dev)                       │         │
│  │                                                               │         │
│  │ 优点:自动解决依赖、版本管理、一键安装                           │         │
│  │ 痛点:不是所有库都在包管理器中                                  │         │
│  │       版本可能不是最新的                                       │         │
│  │       vcpkg 在 Linux 上的体验不如 Conan                        │         │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
│  场景4:系统自带(Linux apt/pacman)                                         │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │ sudo apt install libboost-dev libeigen3-dev libopencv-dev      │         │
│  │                                                               │         │
│  │ 优点:快、方便、系统维护                                        │         │
│  │ 痛点:版本固定(Ubuntu 22.04 的 boost 很旧)                   │         │
│  │       不同发行版版本不同                                       │         │
│  │       Windows 上没这套                                          │         │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

2.1 场景1实战:对接预编译二进制 ​

cmake
# 假设甲方给的文件放在 external/acme_sdk/ 下:
# external/acme_sdk/
# ├── include/acme/api.h
# ├── lib/win64/acme.lib      ← Windows 导入库
# ├── bin/win64/acme.dll       ← Windows DLL
# └── lib/linux64/libacme.so  ← Linux 动态库

cmake_minimum_required(VERSION 3.20)
project(MyApp)

# 方法1:手动指定路径(适合简单场景)
add_executable(my_app main.cpp)

target_include_directories(my_app PRIVATE
    ${CMAKE_SOURCE_DIR}/external/acme_sdk/include
)

if(WIN32)
    target_link_libraries(my_app PRIVATE
        ${CMAKE_SOURCE_DIR}/external/acme_sdk/lib/win64/acme.lib
    )
    # 运行时需要 acme.dll,拷贝到输出目录
    add_custom_command(TARGET my_app POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
        ${CMAKE_SOURCE_DIR}/external/acme_sdk/bin/win64/acme.dll
        $<TARGET_FILE_DIR:my_app>
    )
else()
    target_link_libraries(my_app PRIVATE
        ${CMAKE_SOURCE_DIR}/external/acme_sdk/lib/linux64/libacme.so
    )
endif()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
cmake
# 方法2:创建 IMPORTED 目标(推荐,适合复杂项目)
add_library(acme SHARED IMPORTED)
set_target_properties(acme PROPERTIES
    IMPORTED_LOCATION             "${CMAKE_SOURCE_DIR}/external/acme_sdk/bin/win64/acme.dll"
    IMPORTED_IMPLIB               "${CMAKE_SOURCE_DIR}/external/acme_sdk/lib/win64/acme.lib"
    INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/external/acme_sdk/include"
)
# 之后直接 target_link_libraries(my_app PRIVATE acme) 即可
1
2
3
4
5
6
7
8

2.2 场景2实战:对接源码——FetchContent ​

cmake
# FetchContent = CMake 3.11+ 内置,自动下载 + 编译
include(FetchContent)

FetchContent_Declare(
    spdlog
    GIT_REPOSITORY https://github.com/gabime/spdlog.git
    GIT_TAG        v1.13.0
)
FetchContent_MakeAvailable(spdlog)

# 现在 spdlog 已经编译好了,可以直接用
target_link_libraries(my_app PRIVATE spdlog::spdlog)
1
2
3
4
5
6
7
8
9
10
11
12
┌─────────────────────────────────────────────────────────────────────────────┐
│         FetchContent vs ExternalProject vs add_subdirectory                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  FetchContent:                                                              │
│  • 在 configure 阶段下载源码                                                │
│  • 和你的项目在同一个 build 系统中                                          │
│  • 适合:CMake 项目,依赖会在 CMake 生成时一起处理                           │
│  • 缺点:第一次 configure 慢(要下载)                                       │
│                                                                             │
│  ExternalProject:                                                           │
│  • 在 build 阶段下载和编译(独立进程)                                       │
│  • 适合:非 CMake 项目(autotools、Make、自定义脚本)                        │
│  • 缺点:依赖关系复杂(它是独立构建,你的项目在 configure 时看不到它)        │
│                                                                             │
│  add_subdirectory:                                                          │
│  • 源码已经在你的项目目录里(vendor/ 或 external/ 或 git submodule)          │
│  • 直接将第三方库的 CMakeLists.txt 纳入你的构建                              │
│  • 适合:三方库已经是 CMake 项目                                            │
│  • 缺点:需要手动管理源码更新                                                │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

2.3 场景3实战:包管理器 ​

bash
# vcpkg —— 适合 Windows,也支持 Linux/macOS
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg && ./bootstrap-vcpkg.sh
./vcpkg install spdlog:x64-windows    # Windows
./vcpkg install spdlog:x64-linux      # Linux

# 然后在 CMake 中使用(需要 -DCMAKE_TOOLCHAIN_FILE=...)
cmake -B build -DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake
# 之后直接用 find_package(spdlog CONFIG REQUIRED) 即可
1
2
3
4
5
6
7
8
9
bash
# Conan —— 跨平台,中大型项目首选
# conanfile.txt:
[requires]
spdlog/1.13.0

[generators]
CMakeDeps
CMakeToolchain

# 安装
conan install . --build=missing
1
2
3
4
5
6
7
8
9
10
11

2.4 场景4实战:系统包管理器 (Linux) ​

cmake
# 先 apt install libspdlog-dev
# 然后用 find_package 找到它
find_package(spdlog REQUIRED)
target_link_libraries(my_app PRIVATE spdlog::spdlog)
1
2
3
4

第3部分:find_package 深度解析 ​

3.1 两种模式 ​

find_package 是 CMake 中最强大也最容易迷惑的命令。它有两种完全不同的工作模式:

┌─────────────────────────────────────────────────────────────────────────────┐
│              find_package 的两种模式                                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Module 模式(传统方式):                                                   │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │ find_package(Foo REQUIRED)                                     │         │
│  │                                                               │         │
│  │ CMake 在 CMAKE_MODULE_PATH 中找 FindFoo.cmake                 │         │
│  │ FindFoo.cmake 里手写了:                                      │         │
│  │   • 在哪些目录下找 foo.h                                      │         │
│  │   • 在哪些目录下找 libfoo.so                                  │         │
│  │   • 版本检测、组件检测                                         │         │
│  │                                                               │         │
│  │ CMake 自带了大量 FindXXX.cmake(FindBoost, FindOpenGL, ...)   │         │
│  │ 你也可以自己写 FindXXX.cmake 放在 cmake/ 目录下               │         │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
│  Config 模式(现代方式,推荐):                                             │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │ find_package(Foo CONFIG REQUIRED)                              │         │
│  │ # 或 find_package(Foo REQUIRED) —— 优先 Config,fallback Module│         │
│  │                                                               │         │
│  │ CMake 找 FooConfig.cmake 或 foo-config.cmake                  │         │
│  │ 这个文件由库的作者提供,记录了一切:                            │         │
│  │   • 头文件在哪里                                              │         │
│  │   • 库文件在哪里                                              │         │
│  │   • 依赖了哪些其他库                                           │         │
│  │   • 编译选项(C++标准、宏定义等)                              │         │
│  │                                                               │         │
│  │ 搜索路径:                                                     │         │
│  │   <prefix>/lib/cmake/Foo/        (Linux 标准)                 │         │
│  │   <prefix>/share/Foo/            (Linux 备用)                 │         │
│  │   <prefix>/CMake/                (Windows)                    │         │
│  │   CMAKE_PREFIX_PATH 环境变量或 CMake 变量                     │         │
│  │   Foo_DIR CMake 变量或环境变量                                │         │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
│  实战对比:                                                                  │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │ # 告诉 CMake 去哪找                                            │         │
│  │ cmake -B build -DCMAKE_PREFIX_PATH=/opt/custom_libs           │         │
│  │ # 或针对特定库:                                               │         │
│  │ cmake -B build -DFoo_DIR=/opt/foo/lib/cmake/Foo               │         │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

3.2 一个库的作者如何提供 Config 文件? ​

cmake
# 在你的 CMakeLists.txt 中(编写库的一方)
include(CMakePackageConfigHelpers)

# 安装时生成 MyLibConfig.cmake
install(TARGETS mylib EXPORT MyLibTargets
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
    RUNTIME DESTINATION bin
    INCLUDES DESTINATION include
)
install(EXPORT MyLibTargets
    FILE MyLibTargets.cmake
    NAMESPACE mylib::
    DESTINATION lib/cmake/MyLib
)

# 这样别人用 find_package(MyLib CONFIG) 就能找到
# 然后 target_link_libraries(xxx PRIVATE mylib::mylib)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

第4部分:CMake 核心三件套与依赖传递 ​

4.1 PUBLIC / PRIVATE / INTERFACE ​

这是 CMake 中最核心的概念,也是最容易用错的地方。

┌─────────────────────────────────────────────────────────────────────────────┐
│            PUBLIC / PRIVATE / INTERFACE 的语义                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  假设 B 依赖 A,C 依赖 B:                                                   │
│                                                                             │
│  A ───PRIVATE──→ B ───???──→ C                                              │
│                                                                             │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │                                                               │         │
│  │  PRIVATE(私有依赖):                                         │         │
│  │  • B 在 .cpp 里用了 A 的头文件,但 B 的 .h 里没暴露 A 的类型   │         │
│  │  • C 不需要知道 A 的存在                                      │         │
│  │  • A 的 include 路径只传给 B,不传给 C                        │         │
│  │  • A 的链接只传给 B,不传给 C                                 │         │
│  │                                                               │         │
│  │  PUBLIC(公开依赖):                                          │         │
│  │  • B 的 .h 里 #include 了 A 的头文件                          │         │
│  │  • B 的接口返回或接受 A 中定义的类型                           │         │
│  │  • C 需要能 include 到 A                                      │         │
│  │  • A 的 include 路径和链接都传给 B 和 C                       │         │
│  │                                                               │         │
│  │  INTERFACE(仅接口依赖):                                     │         │
│  │  • B 自己不需要 A(.cpp 不引用 A),但 B 的使用者需要 A         │         │
│  │  • 典型场景:header-only 库、接口库                            │         │
│  │  • A 的 include 路径传给 C,但 B 自己不用                      │         │
│  │                                                               │         │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
│  代码示例:                                                                  │
│  ┌───────────────────────────────────────────────────────────────┐         │
│  │ // B.h - 公开头文件                                            │         │
│  │ #include <A/api.h>  // B 的接口暴露了 A 的类型                 │         │
│  │                                                               │         │
│  │ class B {                                                     │         │
│  │ public:                                                       │         │
│  │     A::Result doSomething();  // 返回值是 A 的类型             │         │
│  │ };                                                             │         │
│  │                                                               │         │
│  │ // 这时候 B 对 A 的依赖必须是 PUBLIC                           │         │
│  │ target_link_libraries(B PUBLIC A)                             │         │
│  │ // 这样 C → B 时,C 自动能 include A 和链接 A                 │         │
│  └───────────────────────────────────────────────────────────────┘         │
│                                                                             │
│  记忆口诀:                                                                  │
│    PRIVATE  = "我只告诉你我知道,但你别告诉别人"                              │
│    PUBLIC   = "我告诉你的,你可以告诉所有人"                                  │
│    INTERFACE = "我不需要,但我的使用者需要"                                   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

4.2 CMake 实战:一个完整的示例 ​

cmake
cmake_minimum_required(VERSION 3.20)
project(MyProject VERSION 1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# ====== 你自己的库 ======
add_library(mycore
    src/core/engine.cpp
    src/core/parser.cpp
)

target_include_directories(mycore
    PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
        $<INSTALL_INTERFACE:include>
    PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/src   # 内部头文件只有自己用
)

# mycore 依赖 spdlog(只在自己的 .cpp 里用 → PRIVATE)
# find_package 或者 FetchContent 获取 spdlog
target_link_libraries(mycore PRIVATE spdlog::spdlog)

# ====== 你的可执行文件 ======
add_executable(my_app src/main.cpp)
target_link_libraries(my_app PRIVATE mycore)  # my_app 只需要知道 mycore
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

第5部分:RPATH 配置最佳实践 ​

5.1 问题场景 ​

你编译了一个程序,它依赖 libmycore.so。默认情况下,程序只在系统路径(/usr/lib 等)找 .so。你的 .so 在 build 目录里,所以运行 ./my_app 会报:

./my_app: error while loading shared libraries: libmycore.so: cannot open shared object file
1

你不想每次都用 LD_LIBRARY_PATH。这就是 RPATH 解决的问题。

cmake
# CMake 中设置 RPATH 的最佳实践
set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib")     # 安装后,.so 在 ../lib 相对位置
set(CMAKE_BUILD_RPATH "$ORIGIN")               # 构建时,.so 和可执行文件在一起

# 或者使用 CMake 3.14+ 的便捷属性:
set_target_properties(my_app PROPERTIES
    BUILD_RPATH          "$ORIGIN"
    INSTALL_RPATH        "$ORIGIN/../lib"
)

# $ORIGIN = 可执行文件所在的目录(Linux)
# 等价于 Windows 上 .exe 所在目录(Windows 默认就在这里搜 DLL,不需要 $ORIGIN)
1
2
3
4
5
6
7
8
9
10
11
12

核心总结 ​

┌─────────────────────────────────────────────────────────────────────────────┐
│                   第三方库集成决策树                                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  甲方给你的形式           →   你的处理方式                                    │
│  ─────────────────────       ──────────────                                  │
│  .h + .lib + .dll           IMPORTED 目标 + 拷贝 DLL 到输出目录              │
│  .h + .so                   IMPORTED 目标 + 设置 RPATH/$ORIGIN              │
│  只有源码 (有CMake)         FetchContent 或 add_subdirectory                │
│  只有源码 (无CMake)         ExternalProject 或自己写 CMakeLists.txt          │
│  包管理器安装               find_package + toolchain file                   │
│  系统 apt 安装              find_package 直接找                              │
│                                                                             │
│  PUBLIC  = 接口暴露了依赖类型(.h 里 #include 了)                             │
│  PRIVATE = 只在 .cpp 里用,对外不可见                                         │
│  INTERFACE = header-only 库或纯接口目标                                      │
│                                                                             │
│  $ORIGIN RPATH = 让你的程序从自己所在目录的 ../lib 加载 .so                    │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

章节测试 ​

测试1:目录结构 ​

你正在开发一个给别人用的 C++ 网络库。你应该用 include/src 分离式还是同目录式布局?为什么?

测试2:imported target vs 手动路径 ​

为什么推荐用 IMPORTED target 而不是在 target_link_libraries 中直接写库的绝对路径?

测试3:依赖传递 ​

库 B 的公开头文件里 #include <A/api.h> 并且返回了 A::Result 类型。B 对 A 的依赖应该用 PUBLIC 还是 PRIVATE?为什么?

测试4:FetchContent vs add_subdirectory ​

你拿到一个第三方库的源码,它有 CMakeLists.txt。用 FetchContent 还是 add_subdirectory(把源码复制到 external/ 下)?各有什么优劣?

测试5:find_package ​

find_package(Boost REQUIRED COMPONENTS filesystem) 这条命令做了什么?是 Module 模式还是 Config 模式?

测试6:RPATH ​

你的可执行文件在 bin/my_app,你的 .so 在 lib/libmycore.so。为了让 my_app 运行时能找到 libmycore.so(无需 LD_LIBRARY_PATH),RPATH 应该设为什么?


参考答案 ​

测试1答案 ​

答案:include/src 分离式。因为你要发布给别人用:

  • include/ 里的头文件是你的公开 API,使用者 #include
  • src/ 里的实现不需要暴露
  • 安装时 include/ → /usr/local/include,清晰分离
  • 如果是内部应用程序,选同目录式更简单

测试2答案 ​

答案:因为 IMPORTED target 是一个"接口对象",可以携带完整的信息:

  • 自动处理 include 路径(INTERFACE_INCLUDE_DIRECTORIES)
  • 自动处理链接依赖(INTERFACE_LINK_LIBRARIES)
  • 可附加编译选项(INTERFACE_COMPILE_DEFINITIONS)
  • 方便 Debug/Release 不同配置切换
  • 如果库有子依赖,手动路径方式需要逐一手动添加

测试3答案 ​

答案:PUBLIC。因为 B 的 .h 文件里包含了 A 的头文件,并且 B 公开接口的返回值使用了 A 中的类型。这意味着任何使用 B 的人(比如 C)也需要能看到 A 的类型定义才能编译通过。用 PUBLIC 可以让 A 的 include 路径和链接自动传递给 C。

测试4答案 ​

答案:

  • FetchContent:自动下载 + 编译,适合 CI 自动化,不需要手动管理源码。缺点:每次 configure 要下载。
  • add_subdirectory:源码已经在本地(复制到 external/ 或用 git submodule),不需要网络。缺点:需要手动维护源码版本更新。
  • 一般推荐 FetchContent(自动化),除非内网环境不能访问外网。

测试5答案 ​

答案:优先尝试 Config 模式(找 BoostConfig.cmake),找不到则 fallback 到 Module 模式(用 CMake 自带的 FindBoost.cmake)。COMPONENTS filesystem 指定了要 Boost 的 filesystem 组件。如果是 find_package(Boost CONFIG ...) 则强制只用 Config 模式。

测试6答案 ​

答案:$ORIGIN/../lib。$ORIGIN = 可执行文件所在目录(即 bin/),../lib 向上找到项目根目录再进 lib/。用 patchelf --set-rpath '$ORIGIN/../lib' bin/my_app 或通过 CMake 的 INSTALL_RPATH 设置。


相关笔记 ​

  • windows build outputs - Windows 编译产物详解
  • linux build outputs - Linux 编译产物详解
  • static vs dynamic linking - 静态链接 vs 动态链接深度对比
  • CMake 构建插件项目 - CMake 构建动态库与插件项目(进阶)

下一步学习 ​

  • [ ] 阅读 04 - 静态链接 vs 动态链接

学习状态:🟡 开始学习

最后更新于:

Pager
下一篇← C++ 编程 / C++ Programming

持续记录,持续成长

Copyright © Tidenflow