【学习笔记】CMake 笔记
项目文件过多,互相 include 关系盘根错节,每次编译得到一个可执行文件时间很长,做微小修改后总是需要重新编译。于是出现了 make,通过 Makefile,对修改的文件进行检测,只重新编译/链接被修改过的文件。学习 Makefile 的书写和修改成本很高,于是出现了 CMake,提供固定的接口(语法规则)来实现 Makefile 的生成。
本文通过某项目的所有 cmake 文件中 985 行命令,解释一下 cmake 的常用指令都在做什么,主要是为了阅读方便来操作,不是说“要学习 CMake 语法,自己书写 CMakeList.txt”。
-
起手式:添加 cmake 最低版本限制、设置项目名称。(如果不执行这两个指令,使用 CMake 生成 Makefile 无法成功)
cmake_minimum_required(VERSION 3.XX)
(这个指令结构比较简单,括号内只有 VERSION 和一个版本号。project(gwtest)
支持指定语言、设置版本、添加描述,分别在括号内加入 LANGUAGES VERSION DESCRIPTION 几个关键字就好了。 也可以省略关键词,这时候这些参数的顺序必须按照 (版本) -> (语言) -> (描述) 的相对顺序 -
将当前 CMakeList.txt 和子文件夹中的 CMakeList.txt 相连接
add_subdirectory(FILE_NAME)
这个指令大大提升了项目结构的可扩展性(增减性),不用再根目录下一个 CMakeList.txt handle 一切、乱七八糟难以调整了。
我的理解是在通过通过 CMakeList.txt 生成 Makefile 时,编译器会将子文件夹的 CMakeList.txt 内容附录到当前的 CMakeList 对应的 add_subdirectory 处。如果加入子文件路径后子文件夹里面没有 CMakeList 会报错。
-
变量设置
set(variable_name value)
value 一定是一个字符串,如果是没有空格的字符串,那么可以不用加引号,比如 set(output_file,a.exe)。
下面是该项目涉及到的 set 命令中定义的变量名称。我觉得可能常用的是编译器的设置
1. 编译器和构建配置
•
CMAKE_CXX_STANDARD 20
设置 C++ 编译标准为 C++20。
CMAKE_CXX_STANDARD_REQUIRED ON
强制要求编译器必须支持指定的 C++ 标准,若不支持则报错。•
CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=deprecated-copy"
向 C++ 编译器标志添加-Wno-error=deprecated-copy
,将弃用拷贝操作的警告不视为错误。•
CMAKE_CXX_FLAGS_RELEASE "${...} -DNDEBUG -O2"
在 Release 模式下添加预定义宏NDEBUG
(禁用断言)和优化选项-O2
。•
CMAKE_BUILD_TYPE Release
指定构建类型为 Release(启用优化,禁用调试符号)。•
CMAKE_POSITION_INDEPENDENT_CODE ON
要求生成位置无关代码(PIC),通常用于构建共享库。
2. 输出路径控制
•
CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${...}/lib"
静态库(.a
/.lib
)的输出目录设为二进制目录下的lib
文件夹。•
CMAKE_RUNTIME_OUTPUT_DIRECTORY ${...}/bin
可执行文件(如.exe
)的输出目录设为二进制目录下的bin
文件夹。•
LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin
共享库(.so
/.dll
)的输出目录设为构建目录下的bin
文件夹(与可执行文件共用)。
3. 链接器配置
•
CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++"
为可执行文件链接器添加静态链接 GCC 和 C++ 标准库的标志。•
CMAKE_SHARED_LINKER_FLAGS "-static-libgcc -static-libstdc++"
为共享库链接器添加相同的静态链接标志。•
CMAKE_BUILD_RPATH "$ORIGIN"
设置运行时库搜索路径为$ORIGIN
(Linux),使得程序运行时能加载同级目录下的共享库。
4. 项目依赖和工具链
•
THIRDPARTY_DIR ${PROJECT_SOURCE_DIR}/thirdparty
定义第三方库的根目录为项目源码目录下的thirdparty
文件夹。•
PROJECT_VERSION ${GOOGLETEST_VERSION}
设置项目版本号为GOOGLETEST_VERSION
的值(此处为 1.10.0)。•
gtest_force_shared_crt ON/OFF
控制 Google Test 是否强制使用共享 C 运行时库(Windows 特有)。
5. 代码生成和导出
•
CMAKE_EXPORT_COMPILE_COMMANDS ON
生成compile_commands.json
文件,供 Clang 工具链(如 clangd)分析代码。•
cmake_package_name GTest
定义 CMake 包的名称为GTest
,用于生成配置文件(如GTestConfig.cmake
)。•
version_file "${...}ConfigVersion.cmake"
定义版本配置文件的路径,用于 CMake 包版本兼容性检查。
-
添加可执行文件
add_executable(output_file_name source_code)
这里的输出文件名也是一个不加引号的字符串,source_code 可以是一个列表(一些以空格隔开的字符串),也可以是一个文件,但是需要保证这些文件里面只有一个 main 函数作为入口
添加可执行文件的过程有很多种,可以非常多样地指定编译选项,输出路径等。在本项目中,我发现可执行文件的属性都是在 add_executable 之后指定的,主要使用了 set_target_properties 、 target_link_libraries 、target_include_directories
-
设置可执行文件的属性
set_target_properties
是 CMake 中用于为已创建的多个目标(如可执行文件、静态库或动态库)设置多个属性的命令。它允许你通过一次调用配置目标的多种行为,覆盖构建、链接、安装等阶段的细节。set_target_properties(target1 target2 ... PROPERTIES property1 value1 property2 value2 ... )
以下是
set_target_properties
中常见的属性及其作用(前置知识,cpp动态库的后缀名是 .so):目录类属性 用途 OUTPUT_NAME
指定目标生成的二进制文件名称(不包含后缀,如 libfoo
而非libfoo.so
)。INSTALL PATH
目标在安装阶段的最终存放路径(如 /usr/local/bin
)。BUILD PATH
目标在构建阶段的临时文件的输出路径(如 build/bin
)。ARCHIVE_OUTPUT_DIRECTORY
静态库( .a/.lib
)的输出目录。LIBRARY_OUTPUT_DIRECTORY
动态库( .so/.dll
)的输出目录。RUNTIME_OUTPUT_DIRECTORY
可执行文件( .exe
)的输出目录。编译器版本属性 用途 VERSION
动态库的版本号(如 1.2.3
)。CXX_STANDARD
指定 C++ 标准(如 11
,14
,17
)。C_STANDARD
指定 C 标准(如 90
,99
,11
)。编译选项类属性 用途 INTERFACE_INCLUDE_DIRECTORIES
声明库的头文件路径,供依赖此库的其他目标使用。 COMPILE_FLAGS
添加编译选项(如 -Wall
),但更推荐用target_compile_options
。LINK_FLAGS
添加链接选项(如 -pthread
),但推荐用target_link_options
。COMPILE_DEFINITIONS
添加预处理器宏定义(如 -DDEBUG
)。IMPORTED_CONFIGURATIONS
指定导入目标(如预编译库)支持的构建类型(如 Debug;Release
)。这里设置了很多编译选项,还有一种编译选项设置是范围设置,对子树里面可执行文件编译过程加某个选项,可以使用 add_compile_options 命令,例如
add_compile_options(-O2 -Wall -fsanitize=address,undefined)
。另有一个命令是 add_compile_definition,这个就是加入宏定义了。
-
向可执行文件添加库。
有一个前置的操作是,进行库的生成:
add_library([target_name] [target_type] [source_code_list])
。这里的库可以指定成动态库 STATIC,静态库 SHARED,插件库 MODULE(运行时加载),依赖(头文件,类的定义)传递 INTERFACE。里面有很多说道,但是我一概不掌握,可以参考下面 deepseek 的说法:
在 CMake 中,
target_include_directories
的PUBLIC
、PRIVATE
和INTERFACE
关键字用于控制头文件包含路径的作用域,决定了这些路径如何传递给依赖当前目标的代码(例如其他库或可执行程序)。以下是它们的核心区别和用法:
1. 作用域分类
关键字 作用 PRIVATE 路径仅用于当前目标的编译(内部实现使用),不会传递给依赖它的其他目标。 PUBLIC 路径既用于当前目标的编译,同时也会传递给依赖它的其他目标(例如公开头文件)。 INTERFACE 路径不用于当前目标的编译,但会传递给依赖它的其他目标(例如纯头文件库的包含路径)。
2. 典型场景示例
场景 1:静态库(Static Library)
假设有一个静态库
mylib
,目录结构如下:project/ ├── include/ # 公共头文件(对外暴露) │ └── mylib.h ├── src/ # 私有实现 │ ├── mylib.cpp │ └── internal.h # 内部头文件
CMakeLists.txt 配置:
add_library(mylib STATIC src/mylib.cpp) # 公共头文件路径:对使用者可见(PUBLIC) target_include_directories(mylib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> ) # 私有头文件路径:仅内部使用(PRIVATE) target_include_directories(mylib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src )
• 效果:
• 当其他目标(如可执行文件)链接mylib
时,会自动获得include/
路径(通过PUBLIC
传递)。
•src/
路径仅用于编译mylib
,不会污染其他目标。
场景 2:动态库(Shared Library)
动态库的作用域规则与静态库相同。例如:
add_library(mylib SHARED src/mylib.cpp) # 公共头文件对外暴露 target_include_directories(mylib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> )
场景 3:纯头文件库(Header-Only Library)
对于仅包含头文件的库(无源文件),使用
INTERFACE
:add_library(header_only INTERFACE) # 头文件路径仅传递给使用者(不用于自身编译) target_include_directories(header_only INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include )
3. 关键区别对比
假设库
A
依赖库B
:作用域 B
的包含路径对A
是否可见?A
的包含路径对B
的使用者是否可见?B 用 PRIVATE ❌ 不可见 ❌ 不可见 B 用 PUBLIC ✅ 可见 ✅ 可见 B 用 INTERFACE ✅ 可见 ❌ 不可见
4. 最佳实践
- 公共头文件:使用
PUBLIC
,确保使用者能正确找到头文件。 - 私有头文件:使用
PRIVATE
,避免暴露实现细节。 - 头文件库:使用
INTERFACE
,避免路径被自身编译使用。 - 避免全局作用域:不要滥用
include_directories()
,优先用target_include_directories
。
5. **完整示例
假设有以下依赖链:
可执行程序 (app) → 静态库 (mylib) → 内部工具库 (utils)
CMakeLists.txt 配置:
# 内部工具库 utils(私有实现) add_library(utils STATIC src/utils.cpp) target_include_directories(utils PRIVATE src/utils) # 主库 mylib(依赖 utils) add_library(mylib STATIC src/mylib.cpp) target_include_directories(mylib PUBLIC include # 公共头文件 PRIVATE src/mylib # 私有头文件 ) target_link_libraries(mylib PRIVATE utils) # 仅内部依赖 # 可执行程序 app(依赖 mylib) add_executable(app main.cpp) target_link_libraries(app PUBLIC mylib) # 自动获得 mylib 的 PUBLIC 头文件路径
• 结果:
•app
自动获得mylib
的include/
路径。
•app
不会获得utils
的私有头文件路径(因为utils
是PRIVATE
链接到mylib
)。
通过合理使用
PUBLIC
/PRIVATE
,可以精准控制依赖传递,避免头文件路径污染和编译错误!add_library 的过程中也实现了对库的编译,接下来可以描述可执行文件都需要引用哪些文件。这些文件将在编译的第一步被直接粘贴到源代码中。
target_include_directories(file_name [TYPE_NAME1] lib_A lib_B ... [TYPE_NAME2] ... ... )
这个命令就设置了 file_name 对象的 include 地址。
- 公共头文件:使用
-
指明可执行文件所使用的库的链接情况
和上面的情况类似。这里 TYPE_NAME 这个关键词的相关用法和上面 add_library 处给的 deepseek response 保持一致。
target_link_libraries(file_name, [TYPE_NAME1] lib_A lib_B ... )
-
install add_dependency 和 find_package
来自 deepseek.
在 CMake 中,`install`、`find_package` 和 `add_dependencies` 是管理项目构建、依赖和安装的关键命令。以下是它们的详细用法和示例:
1.
install
:安装目标或文件到指定路径作用:将构建生成的目标(如可执行文件、库)或普通文件(如头文件、配置文件)安装到指定目录,便于分发或供其他项目使用。
基本语法:
install(TARGETS <目标名> DESTINATION <路径>) install(FILES <文件列表> DESTINATION <路径>) install(DIRECTORY <目录路径> DESTINATION <路径>)
常见用法:
• 安装可执行文件:
install(TARGETS my_app DESTINATION bin)
• 安装库和头文件:
install(TARGETS my_lib DESTINATION lib) install(FILES my_header.h DESTINATION include)
• 安装整个目录(如资源文件):
install(DIRECTORY resources/ DESTINATION share/my_project)
高级用法:
• 导出目标供其他项目使用:
install(TARGETS my_lib EXPORT MyLibTargets DESTINATION lib INCLUDES DESTINATION include ) install(EXPORT MyLibTargets DESTINATION lib/cmake/MyLib)
这会生成
MyLibTargets.cmake
,其他项目可通过find_package
导入。配置安装路径:
使用
CMAKE_INSTALL_PREFIX
指定根目录:cmake -B build -DCMAKE_INSTALL_PREFIX=/usr/local cmake --build build --target install
2.
find_package
:查找外部依赖作用:定位系统或用户提供的第三方库,自动设置包含路径、库路径等变量。
基本语法:
find_package(<包名> [版本] [REQUIRED] [COMPONENTS 组件...])
查找模式:
• 模块模式:查找
Find<包名>.cmake
文件(通常位于CMAKE_MODULE_PATH
)。
• 配置模式:查找<包名>Config.cmake
或<包名>-config.cmake
(通常在<前缀>/lib/cmake
中)。示例:
find_package(OpenCV 4.0 REQUIRED COMPONENTS core videoio) include_directories(${OpenCV_INCLUDE_DIRS}) target_link_libraries(my_app ${OpenCV_LIBRARIES})
自定义查找路径:
• 设置
CMAKE_PREFIX_PATH
或PackageName_DIR
变量:set(CMAKE_PREFIX_PATH "/path/to/opencv") find_package(OpenCV REQUIRED)
常见变量:
•
<包名>_FOUND
:是否找到包。
•<包名>_INCLUDE_DIRS
:头文件路径。
•<包名>_LIBRARIES
:库文件路径。
3.
add_dependencies
:显式添加目标依赖作用:确保某个目标在构建另一个目标之前先被构建。通常用于处理非自动推断的依赖关系(如自定义命令生成的文件)。
基本语法:
add_dependencies(<目标> <依赖项>)
常见场景:
• 依赖自定义目标:
add_custom_target(generate_files COMMAND ...) add_executable(my_app main.cpp) add_dependencies(my_app generate_files) # 确保 generate_files 先构建
• 跨项目依赖(如通过
add_subdirectory
引入的子项目):add_subdirectory(lib_project) add_executable(my_app main.cpp) add_dependencies(my_app lib_project_target)
注意:
• 与
target_link_libraries
的区别:
target_link_libraries
会自动添加库依赖,但add_dependencies
用于非链接依赖(如确保生成文件存在)。
CMake 还有一个可变的因素就是构建配置(build configuration),主要有Debug,Release,RelWithDebInfo,MinSizeRel四种,考虑 debug release 两种,下面是 deepseek 的一些讲解:
CMake 中 release mode 和 debug mode 分别有什么区别
1. 优化级别(Optimization Level)
• Release 模式
• 优化目标:最大化运行效率(速度/体积)。
• 编译器选项:通常启用高级优化(如 -O3
或 /O2
)。
• 副作用:代码可能难以调试(优化后的代码可能与源码不对应)。
• Debug 模式
• 优化目标:保留调试信息,禁用优化。
• 编译器选项:通常关闭优化(-O0
或 /Od
)。
• 副作用:生成的可执行文件较大且运行较慢,但便于调试。
2. 调试信息(Debug Symbols)
• Release 模式
• 默认行为:不生成调试符号(如 .pdb
文件)。
• 可配置性:可手动启用(但会增加文件体积)。
• Debug 模式
• 默认行为:生成完整的调试符号(用于 GDB、LLDB 或 Visual Studio 调试器)。
• 重要性:允许断点、变量检查等调试操作。
3. 断言(Assertions)
• Release 模式
• 默认行为:禁用 assert()
宏(通过 NDEBUG
宏定义)。
• 目的:避免性能损耗。
• Debug 模式
• 默认行为:启用 assert()
宏(未定义 NDEBUG
)。
• 目的:帮助开发者快速发现逻辑错误。
4. 其他编译器选项
• Release 模式
• 可能启用链接时优化(LTO, -flto
)。
• 可能删除未使用的代码和符号(-fdata-sections -ffunction-sections
+ --gc-sections
)。
• Debug 模式
• 启用运行时检查(如数组越界检查 /RTC1
)。
• 禁用内联展开(保留完整的函数调用栈)。
5. CMake 中的配置
设置构建类型
在 CMake 中,通过 CMAKE_BUILD_TYPE
变量指定构建模式:
# 在命令行中指定
cmake -DCMAKE_BUILD_TYPE=Release ..
# 或在 CMakeLists.txt 中设置默认值
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()
条件化配置
可通过生成器表达式(Generator Expressions)根据构建类型动态设置编译选项:
add_compile_definitions(
"$<$<CONFIG:Debug>:DEBUG_MODE>"
"$<$<CONFIG:Release>:RELEASE_MODE>"
)
6. 适用场景
模式 | 适用场景 |
---|---|
Debug | 开发阶段、调试崩溃问题、内存泄漏检查(如 Valgrind)。 |
Release | 性能测试、最终发布版本、用户交付。 |
7. 性能与调试对比
特性 | Debug 模式 | Release 模式 |
---|---|---|
运行速度 | 慢(无优化) | 快(高度优化) |
文件体积 | 大(含调试符号) | 小(无冗余信息) |
调试支持 | 完整(断点、变量查看) | 困难(代码被优化) |
安全性检查 | 启用(如越界检查) | 禁用(牺牲检查换速度) |
最佳实践
- 开发阶段用 Debug:确保可调试性。
- 发布前切 Release:测试性能并验证优化后的逻辑正确性。
- 避免混合配置:不要将 Debug 库与 Release 可执行文件混用(可能导致内存错误)。
还有什么别的 mode 吗
除了 Debug
和 Release
,CMake 还支持其他预定义的构建模式(Build Types),开发者也可以自定义模式。以下是常见的模式及其用途:
一、CMake 内置的标准模式
1. RelWithDebInfo (Release with Debug Info)
• 特点:
• 启用优化(类似 Release
,默认 -O2
或 /O2
)。
• 保留调试符号(可调试,但文件体积较大)。
• 用途:
• 性能接近 Release
,同时保留调试信息用于生产环境的问题追踪。
2. MinSizeRel (Minimal Size Release)
• 特点:
• 优化目标为最小化二进制体积(如 -Os
或 /O1
)。
• 不保留调试符号。
• 用途:
• 适用于对磁盘/内存占用敏感的场景(如嵌入式设备)。
3. 其他标准模式
• None:无默认优化或调试标志(需手动配置)。
• Custom:用户完全自定义(依赖项目或工具链定义)。
二、常见自定义模式
开发者可通过设置 CMAKE_CXX_FLAGS_<MODE>
等变量扩展自定义模式,例如:
1. Profile (性能分析)
• 特点:
• 启用优化(如 -O2
)。
• 添加性能分析标志(如 -pg
生成 gprof
数据)。
• 用途:
• 用于代码性能剖析(Profiling)。
2. Sanitizer (内存/线程检测)
• 特点:
• 启用 AddressSanitizer (-fsanitize=address
) 或 ThreadSanitizer (-fsanitize=thread
)。
• 通常关闭优化以保留检测精度。
• 用途:
• 检测内存泄漏、越界访问、竞态条件等。
3. Coverage (代码覆盖率)
• 特点:
• 添加覆盖率检测标志(如 --coverage
或 -fprofile-arcs -ftest-coverage
)。
• 生成 .gcda
/.gcno
文件供 lcov
或 gcov
分析。
• 用途:
• 统计单元测试的代码覆盖率。
三、模式对比表
模式 | 优化级别 | 调试符号 | 典型用途 |
---|---|---|---|
Debug | -O0 |
保留 | 开发调试 |
Release | -O3 |
不保留 | 生产环境 |
RelWithDebInfo | -O2 |
保留 | 生产环境问题追踪 |
MinSizeRel | -Os |
不保留 | 最小化体积 |
Profile | -O2 |
保留 | 性能分析 |
Sanitizer | -O0 |
保留 | 内存/线程安全检查 |
Coverage | -O0 |
保留 | 测试覆盖率统计 |
四、如何使用这些模式
1. 命令行指定
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
make
2. 在 CMakeLists.txt
中设置默认值
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug CACHE STRING "选择构建类型" FORCE)
endif()
3. 自定义模式配置
# 定义自定义模式 "Profile"
set(CMAKE_CXX_FLAGS_PROFILE "-O2 -pg")
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "-pg")
五、注意事项
- 模式兼容性:不同编译器支持的优化/调试标志可能不同(如 GCC 与 MSVC)。
- 依赖管理:确保依赖库的构建模式与主项目一致(避免 Debug/Release 混用导致的崩溃)。
- 跨平台:自定义模式需考虑不同操作系统(Linux/macOS/Windows)的编译差异。
通过合理选择或定义构建模式,可以更高效地适配开发、测试和生产环境的需求。
某项目中的部分代码源文件,如果出于各种原因不宜直接传递,可以将其编译为动态库,再进行传递。将 .cxx 文件中的具体实现编译成 .so,将 .hxx 文件中的定义加注释,正常传递,甚至不影响阅读。编译时直接对动态库进行链接即可。
下面是 deepseek 对 static library/shared library/head-only library 的一些解读。
在C++中,静态库、动态库和仅头文件库是三种常见的代码复用方式,它们各有特点和应用场景。以下是它们的详细对比:
1. 静态库(Static Library)
• 定义
静态库在编译时(链接阶段)直接嵌入到可执行文件中。代码会被完整复制到最终的可执行文件内。
• 文件格式:Windows 为 .lib
(与动态库的导入库同名,需注意区分),Linux 为 .a
。
• 示例工具:ar
(Linux)、lib.exe
(Windows)。
• 优点
• 无运行时依赖:程序独立运行,无需额外库文件。
• 性能优化:编译时链接允许链接器优化代码。
• 版本兼容性:避免动态库版本冲突问题。
• 缺点
• 体积大:每个使用静态库的程序都包含库代码的副本。
• 更新困难:库更新需重新编译整个程序。
• 适用场景
需要独立部署、避免依赖或对性能敏感的场景(如嵌入式系统)。
2. 动态库(Dynamic Library / Shared Library)
• 定义
动态库在程序运行时加载,代码不会被复制到可执行文件中,而是由操作系统动态链接。
• 文件格式:Windows 为 .dll
,Linux 为 .so
。
• 加载方式:隐式加载(启动时自动加载)或显式加载(通过 LoadLibrary
/dlopen
手动加载)。
• 优点
• 节省资源:多个程序共享同一物理文件,减少内存和磁盘占用。
• 更新灵活:替换动态库文件即可更新功能(需保持 ABI 兼容)。
• 缺点
• 依赖管理:程序运行时需确保动态库存在且版本兼容。
• 性能开销:首次加载或符号解析可能带来轻微延迟。
• 适用场景
系统级共享库(如 C 标准库 libc.so
)或插件化架构。
3. 仅头文件库(Header-only Library)
• 定义
所有实现代码均位于头文件(.h
或 .hpp
)中,无需编译,直接通过 #include
使用。
• 示例库:Eigen(线性代数)、spdlog(日志库)、Boost.Asio(部分功能)。
• 优点
• 跨平台:无编译依赖,使用简单。
• 模板友好:适合需要编译时实例化的模板代码。
• 缺点
• 编译时间长:头文件内容在每次编译时都会处理。
• 代码膨胀:模板可能导致生成的多份实例增大二进制体积。
• 适用场景
轻量级库、模板库或需要快速原型设计的项目。
三者的关键区别
特性 | 静态库 | 动态库 | 仅头文件库 |
---|---|---|---|
整合时机 | 编译时(链接阶段) | 运行时 | 编译时(预处理阶段) |
代码复制 | 复制到可执行文件 | 不复制,动态加载 | 复制到每个调用处 |
运行时依赖 | 无 | 必须存在对应库文件 | 无 |
二进制体积 | 较大(含库代码) | 较小 | 可能较大(模板实例化) |
更新维护 | 需重新编译程序 | 替换库文件(ABI 兼容) | 修改头文件后重新编译 |
适用场景 | 独立部署、无依赖 | 资源共享、插件化 | 模板库、快速开发 |
总结
• 静态库:适合追求独立性和性能,避免运行时依赖的场景。
• 动态库:适合资源共享、频繁更新或需要插件化架构的项目。
• 仅头文件库:适合轻量级或模板代码,简化跨平台部署。
根据项目需求(如部署环境、维护成本、性能要求)合理选择库类型,可以显著提升开发效率和程序质量。
理论上某项目还使用了 GoogleTest,GTEST 里面涉及了大量复杂命令,但是我先不掌握了。