库的基础概念
什么是库
库(Library)是预先编译好的代码集合,可以被多个程序复用。它将常用功能封装起来,避免重复编写代码。
库的作用:
- 代码复用:一次编写,多处使用
- 模块化:将大型项目拆分为独立模块
- 封装实现:隐藏实现细节,只暴露接口
静态库 vs 动态库
| 特性 | 静态库(Static Library) | 动态库(Shared Library) |
|---|
| 文件扩展名 | Linux/macOS: .a Windows MSVC: .lib Windows MinGW: .a | Linux: .so macOS: .dylib Windows MSVC: .dll Windows MinGW 导入库: .dll.a |
| 链接时机 | 编译时链接到可执行文件 | 运行时动态加载 |
| 文件大小 | 可执行文件较大(包含库代码) | 可执行文件较小(不包含库代码) |
| 内存占用 | 每个程序独立占用内存 | 多个程序共享同一份库代码 |
| 更新方式 | 需要重新编译可执行文件 | 只需替换库文件 |
| 依赖管理 | 无运行时依赖 | 需要确保库文件存在 |
文件命名规则:
| 平台/编译器 | 静态库 | 动态库(运行时) | 导入库(链接时) |
|---|
| Linux GCC | libxxx.a | libxxx.so | 无(直接链接 .so) |
| macOS Clang | libxxx.a | libxxx.dylib | 无(直接链接 .dylib) |
| Windows MSVC | xxx.lib | xxx.dll | xxx.lib |
| Windows MinGW | libxxx.a | libxxx.dll | libxxx.dll.a |
💡 说明:Windows 下动态库需要两个文件:
- 运行时文件(
.dll):程序运行时加载 - 导入库(MSVC 的
.lib 或 MinGW 的 .dll.a):编译链接时使用
CMake 中的库类型
1 2 3 4 5 6 7 8 9 10 11
| add_library(mylib STATIC src1.cpp src2.cpp)
add_library(mylib SHARED src1.cpp src2.cpp)
add_library(mylib INTERFACE)
add_library(mylib OBJECT src1.cpp src2.cpp)
|
静态库(Static Library)
什么是静态库
静态库在编译时被完整复制到可执行文件中。链接器会将库中被使用的函数代码提取出来,合并到最终的可执行文件。
优点:
- 无运行时依赖,部署简单
- 运行速度稍快(无动态加载开销)
缺点:
- 可执行文件体积大
- 更新库需要重新编译所有程序
- 多个程序使用同一库时,内存占用高
创建静态库
项目结构:
1 2 3 4 5 6 7 8
| mathlib/ ├── CMakeLists.txt ├── include/ │ └── mathlib/ │ └── operations.h └── src/ ├── add.cpp └── multiply.cpp
|
include/mathlib/operations.h:
1 2 3 4 5 6
| #pragma once
namespace mathlib { int add(int a, int b); int multiply(int a, int b); }
|
src/add.cpp:
1 2 3 4 5 6 7
| #include "mathlib/operations.h"
namespace mathlib { int add(int a, int b) { return a + b; } }
|
src/multiply.cpp:
1 2 3 4 5 6 7
| #include "mathlib/operations.h"
namespace mathlib { int multiply(int a, int b) { return a * b; } }
|
CMakeLists.txt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| cmake_minimum_required(VERSION 3.15) project(MathLib VERSION 1.0.0)
add_library(mathlib STATIC src/add.cpp src/multiply.cpp )
target_include_directories(mathlib PUBLIC ${PROJECT_SOURCE_DIR}/include )
target_compile_features(mathlib PUBLIC cxx_std_17)
|
使用静态库:
1 2 3
| add_executable(myapp main.cpp) target_link_libraries(myapp PRIVATE mathlib)
|
静态库最佳实践
1. 头文件组织
1 2 3 4 5 6 7 8
| include/ └── mylib/ # 库名作为命名空间 ├── core.h # 公共接口 └── utils.h src/ ├── core.cpp # 实现文件 ├── utils.cpp └── internal.h # 私有头文件(不安装)
|
2. 接口设计原则
- 使用命名空间避免符号冲突
- 头文件使用
#pragma once 或 include guard - 最小化头文件依赖(前向声明)
- 使用 PIMPL 模式隐藏实现细节
3. 常见问题
- 符号重复定义:确保函数只在
.cpp 中实现,头文件只声明 - 链接顺序:被依赖的库放在后面(现代 CMake 自动处理)
- 循环依赖:重新设计模块边界,避免 A 依赖 B 且 B 依赖 A
4. 循环依赖的解决方案
循环依赖是指两个或多个库相互依赖,这会导致链接错误和设计问题。
❌ 错误示例:
1 2 3 4 5
| add_library(libA ...) target_link_libraries(libA PUBLIC libB)
add_library(libB ...) target_link_libraries(libB PUBLIC libA)
|
✅ 解决方案:
方案 1:提取公共部分
1 2 3 4 5 6 7 8
| add_library(common ...)
add_library(libA ...) target_link_libraries(libA PUBLIC common)
add_library(libB ...) target_link_libraries(libB PUBLIC common)
|
方案 2:使用前向声明
1 2 3 4 5 6
| class ClassB;
class ClassA { ClassB* ptr; };
|
方案 3:重新设计模块边界
- 分析依赖关系,确定哪个库应该是底层库
- 将高层功能移到上层库,保持单向依赖
动态库(Shared Library)
什么是动态库
动态库在运行时被加载到内存,多个程序可以共享同一份库代码。
优点:
- 可执行文件体积小
- 更新库无需重新编译程序
- 多个程序共享内存,节省资源
- 支持插件式架构
缺点:
- 需要管理运行时依赖
- 版本兼容性问题(DLL Hell)
- 启动时有动态加载开销
创建动态库
项目结构:
1 2 3 4 5 6 7
| stringlib/ ├── CMakeLists.txt ├── include/ │ └── stringlib/ │ └── string_util.h └── src/ └── string_util.cpp
|
include/stringlib/string_util.h:
1 2 3 4 5 6 7 8
| #pragma once #include "stringlib/stringlib_export.h" #include <string>
namespace stringlib { STRINGLIB_EXPORT std::string to_upper(const std::string& str); STRINGLIB_EXPORT std::string to_lower(const std::string& str); }
|
src/string_util.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include "stringlib/string_util.h" #include <algorithm>
namespace stringlib { std::string to_upper(const std::string& str) { std::string result = str; std::transform(result.begin(), result.end(), result.begin(), ::toupper); return result; }
std::string to_lower(const std::string& str) { std::string result = str; std::transform(result.begin(), result.end(), result.begin(), ::tolower); return result; } }
|
CMakeLists.txt:
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
| cmake_minimum_required(VERSION 3.15) project(StringLib VERSION 1.0.0)
include(GenerateExportHeader)
add_library(stringlib SHARED src/string_util.cpp )
generate_export_header(stringlib EXPORT_FILE_NAME include/stringlib/stringlib_export.h )
target_include_directories(stringlib PUBLIC ${PROJECT_SOURCE_DIR}/include ${CMAKE_CURRENT_BINARY_DIR}/include )
set_target_properties(stringlib PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} )
target_compile_features(stringlib PUBLIC cxx_std_17)
|
动态库特殊配置
1. 符号导出控制(GenerateExportHeader)
Windows 默认不导出符号,需要显式标记。推荐使用 CMake 的 GenerateExportHeader 模块,这是业界标准做法。
工作原理:
CMake 自动生成跨平台的导出宏头文件,处理 Windows 的 __declspec(dllexport/dllimport) 和 Linux 的 __attribute__((visibility))。
完整示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| include(GenerateExportHeader)
add_library(mylib SHARED src/mylib.cpp)
generate_export_header(mylib EXPORT_FILE_NAME include/mylib/mylib_export.h )
target_include_directories(mylib PUBLIC ${PROJECT_SOURCE_DIR}/include ${CMAKE_CURRENT_BINARY_DIR}/include )
|
使用生成的导出宏:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #pragma once #include "mylib/mylib_export.h"
class MYLIB_EXPORT MyClass { public: void public_method(); private: void private_method(); };
MYLIB_EXPORT void my_function();
void internal_helper();
|
生成的 mylib_export.h 内容(自动生成):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #ifndef MYLIB_EXPORT_H #define MYLIB_EXPORT_H
#ifdef MYLIB_STATIC_DEFINE # define MYLIB_EXPORT #else # ifndef MYLIB_EXPORT # ifdef mylib_EXPORTS # define MYLIB_EXPORT __declspec(dllexport) # else # define MYLIB_EXPORT __declspec(dllimport) # endif # endif #endif
#endif
|
优点:
- ✅ 自动处理跨平台差异(Windows/Linux/macOS)
- ✅ 自动处理静态库/动态库切换
- ✅ 只导出标记的符号,私有函数不导出
- ✅ 业界标准(Qt、KDE、VTK 等大型项目都使用)
- ✅ 减少手动维护的代码
高级选项:
1 2 3 4 5 6
| generate_export_header(mylib EXPORT_FILE_NAME include/mylib/mylib_export.h EXPORT_MACRO_NAME MYLIB_API NO_EXPORT_MACRO_NAME MYLIB_PRIVATE DEPRECATED_MACRO_NAME MYLIB_DEPRECATED )
|
2. 版本号管理
1 2 3 4
| set_target_properties(mylib PROPERTIES VERSION 2.3.1 SOVERSION 2 )
|
- VERSION:完整版本号,用于文件名
- SOVERSION:ABI 兼容版本号,主版本号变化表示不兼容
3. RPATH 配置
Linux/macOS 下,可执行文件需要知道动态库的位置:
1 2 3 4 5 6 7
| set_target_properties(myapp PROPERTIES INSTALL_RPATH "$ORIGIN/../lib" )
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
4. 安装配置
1 2 3 4 5 6 7 8 9 10 11
| install(TARGETS stringlib LIBRARY DESTINATION lib ARCHIVE DESTINATION lib RUNTIME DESTINATION bin )
install(DIRECTORY include/ DESTINATION include )
|
库的链接与使用
target_link_libraries 基础
基本语法:
1 2 3
| target_link_libraries(<target> <PRIVATE|PUBLIC|INTERFACE> <lib1> [<lib2> ...] )
|
示例:链接库到可执行文件
1 2 3 4 5 6 7 8
| add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mathlib)
target_link_libraries(myapp PRIVATE mathlib stringlib)
|
示例:库之间的链接
1 2 3 4 5 6 7 8 9 10 11
| add_library(core STATIC core.cpp)
add_library(utils STATIC utils.cpp) target_link_libraries(utils PUBLIC core)
add_executable(app main.cpp) target_link_libraries(app PRIVATE utils)
|
链接可见性
可见性控制依赖的传递方式,这是现代 CMake 的核心概念。
三种可见性:
| 可见性 | 含义 | 使用场景 |
|---|
| PRIVATE | 仅目标自己使用 | 实现细节,不暴露给依赖者 |
| PUBLIC | 目标和依赖者都能使用 | 接口的一部分,需要传递给依赖者 |
| INTERFACE | 仅依赖者使用,目标自己不使用 | 仅头文件库,或仅传递编译选项 |
图解说明:
1 2 3 4 5 6 7 8 9 10 11
| 场景 1:PRIVATE [App] --链接--> [LibA] --PRIVATE链接--> [LibB] 结果:App 只能使用 LibA,看不到 LibB
场景 2:PUBLIC [App] --链接--> [LibA] --PUBLIC链接--> [LibB] 结果:App 可以使用 LibA 和 LibB(自动传递)
场景 3:INTERFACE [App] --链接--> [HeaderOnlyLib] --INTERFACE链接--> [LibC] 结果:App 可以使用 LibC,但 HeaderOnlyLib 自己不编译任何代码
|
实际例子:
1 2 3 4 5 6 7 8 9 10 11 12
| add_library(json STATIC json_parser.cpp)
add_library(network STATIC network.cpp)
target_link_libraries(network PUBLIC json)
target_link_libraries(network PRIVATE json)
|
选择原则:
- 依赖出现在头文件的公共接口中 →
PUBLIC - 依赖仅在实现文件中使用 →
PRIVATE - 仅头文件库或仅传递编译选项 →
INTERFACE
⚠️ 避免使用全局 link_libraries
❌ 错误做法:
1 2 3
| link_libraries(Boost::filesystem) add_executable(app1 ...) add_executable(app2 ...)
|
这会导致所有后续定义的目标都链接 Boost::filesystem,即使它们不需要。
✅ 正确做法:
1 2 3 4 5
| add_executable(app1 ...) target_link_libraries(app1 PRIVATE Boost::filesystem)
add_executable(app2 ...) target_link_libraries(app2 PRIVATE Boost::system)
|
原因:
link_libraries 是全局命令,影响所有后续目标target_link_libraries 是目标级别命令,只影响指定目标- 使用目标级别命令可以精确控制每个目标的依赖,避免不必要的链接
target_include_directories
控制头文件搜索路径,与 target_link_libraries 配合使用。
语法:
1 2 3
| target_include_directories(<target> <PRIVATE|PUBLIC|INTERFACE> <dir1> [<dir2> ...] )
|
可见性说明:
| 可见性 | 含义 | 使用场景 |
|---|
| PRIVATE | 仅目标自己使用 | 内部实现的头文件 |
| PUBLIC | 目标和依赖者都能使用 | 库的公共接口头文件 |
| INTERFACE | 仅依赖者使用 | 仅头文件库(Header-only) |
完整示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| add_library(mylib src/mylib.cpp)
target_include_directories(mylib PRIVATE ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/third_party )
target_include_directories(mylib PUBLIC ${PROJECT_SOURCE_DIR}/include )
|
生成器表达式(Generator Expressions):
处理构建时和安装后的路径差异:
1 2 3 4
| target_include_directories(mylib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include> )
|
链接外部库
1. 链接系统库
1 2 3 4 5 6 7 8 9
| target_link_libraries(myapp PRIVATE m)
find_package(Threads REQUIRED) target_link_libraries(myapp PRIVATE Threads::Threads)
target_link_libraries(myapp PRIVATE dl)
|
2. 使用 find_package 查找第三方库
1 2 3 4 5 6 7 8 9 10 11
| find_package(OpenCV REQUIRED) target_link_libraries(myapp PRIVATE OpenCV::OpenCV)
find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system) target_link_libraries(myapp PRIVATE Boost::filesystem Boost::system)
find_package(OpenMP REQUIRED) target_link_libraries(myapp PRIVATE OpenMP::OpenMP_CXX)
|
3. 手动指定库路径(不推荐)
1 2 3 4 5 6 7 8 9
| link_directories(/usr/local/lib)
target_link_libraries(myapp PRIVATE /usr/local/lib/libfoo.a)
find_library(FOO_LIB foo PATHS /usr/local/lib) target_link_libraries(myapp PRIVATE ${FOO_LIB})
|
实战案例
多库项目
场景:创建一个包含多个库的项目,演示库之间的依赖关系。
项目结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| multi_lib_project/ ├── CMakeLists.txt ├── core/ │ ├── CMakeLists.txt │ ├── include/core/ │ │ └── logger.h │ └── src/ │ └── logger.cpp ├── utils/ │ ├── CMakeLists.txt │ ├── include/utils/ │ │ └── string_helper.h │ └── src/ │ └── string_helper.cpp └── app/ ├── CMakeLists.txt └── main.cpp
|
core/include/core/logger.h:
1 2 3 4 5 6
| #pragma once #include <string>
namespace core { void log(const std::string& message); }
|
core/src/logger.cpp:
1 2 3 4 5 6 7 8
| #include "core/logger.h" #include <iostream>
namespace core { void log(const std::string& message) { std::cout << "[LOG] " << message << std::endl; } }
|
core/CMakeLists.txt:
1 2 3 4 5 6 7 8 9
| add_library(core STATIC src/logger.cpp )
target_include_directories(core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include )
target_compile_features(core PUBLIC cxx_std_17)
|
utils/include/utils/string_helper.h:
1 2 3 4 5 6
| #pragma once #include <string>
namespace utils { std::string trim(const std::string& str); }
|
utils/src/string_helper.cpp:
1 2 3 4 5 6 7 8 9 10 11 12
| #include "utils/string_helper.h" #include "core/logger.h"
namespace utils { std::string trim(const std::string& str) { core::log("Trimming string"); size_t start = str.find_first_not_of(" \t\n\r"); size_t end = str.find_last_not_of(" \t\n\r"); return (start == std::string::npos) ? "" : str.substr(start, end - start + 1); } }
|
utils/CMakeLists.txt:
1 2 3 4 5 6 7 8 9 10 11 12
| add_library(utils STATIC src/string_helper.cpp )
target_include_directories(utils PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include )
target_link_libraries(utils PRIVATE core)
target_compile_features(utils PUBLIC cxx_std_17)
|
app/main.cpp:
1 2 3 4 5 6 7 8 9 10 11
| #include "utils/string_helper.h" #include "core/logger.h" #include <iostream>
int main() { std::string text = " Hello, CMake! "; std::string result = utils::trim(text); core::log("Result: " + result); return 0; }
|
app/CMakeLists.txt:
1 2 3 4
| add_executable(app main.cpp)
target_link_libraries(app PRIVATE utils core)
|
根 CMakeLists.txt:
1 2 3 4 5 6 7
| cmake_minimum_required(VERSION 3.15) project(MultiLibProject VERSION 1.0.0)
add_subdirectory(core) add_subdirectory(utils) add_subdirectory(app)
|
编译运行:
1 2 3 4
| mkdir build && cd build cmake .. cmake --build . ./app/app
|
输出:
1 2
| [LOG] Trimming string [LOG] Result: Hello, CMake!
|
关键点:
core 是基础库,不依赖其他库utils 依赖 core,使用 PRIVATE 因为 core 不出现在 utils 的公共接口中app 同时链接 utils 和 core
场景:创建一个跨平台的动态库和仅头文件库。
项目结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| cross_platform_lib/ ├── CMakeLists.txt ├── shared_lib/ │ ├── CMakeLists.txt │ ├── include/ │ │ └── calc/ │ │ └── calculator.h │ └── src/ │ └── calculator.cpp ├── header_only_lib/ │ ├── CMakeLists.txt │ └── include/ │ └── templates/ │ └── array_utils.h └── app/ ├── CMakeLists.txt └── main.cpp
|
shared_lib/include/calc/calculator.h:
1 2 3 4 5 6 7 8 9 10
| #pragma once #include "calc/calc_export.h"
namespace calc { class CALC_EXPORT Calculator { public: double add(double a, double b); double subtract(double a, double b); }; }
|
shared_lib/src/calculator.cpp:
1 2 3 4 5 6 7 8 9 10 11
| #include "calc/calculator.h"
namespace calc { double Calculator::add(double a, double b) { return a + b; }
double Calculator::subtract(double a, double b) { return a - b; } }
|
shared_lib/CMakeLists.txt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| include(GenerateExportHeader)
add_library(calc SHARED src/calculator.cpp )
generate_export_header(calc EXPORT_FILE_NAME include/calc/calc_export.h )
target_include_directories(calc PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_BINARY_DIR}/include )
set_target_properties(calc PROPERTIES VERSION 1.0.0 SOVERSION 1 )
target_compile_features(calc PUBLIC cxx_std_17)
|
header_only_lib/include/templates/array_utils.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #pragma once #include <algorithm> #include <vector>
namespace templates { template<typename T> T max_element(const std::vector<T>& vec) { return *std::max_element(vec.begin(), vec.end()); }
template<typename T> T min_element(const std::vector<T>& vec) { return *std::min_element(vec.begin(), vec.end()); } }
|
header_only_lib/CMakeLists.txt:
1 2 3 4 5 6 7 8
| add_library(array_utils INTERFACE)
target_include_directories(array_utils INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include )
target_compile_features(array_utils INTERFACE cxx_std_17)
|
app/main.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include "calc/calculator.h" #include "templates/array_utils.h" #include <iostream> #include <vector>
int main() { calc::Calculator calculator; std::cout << "10 + 5 = " << calculator.add(10, 5) << std::endl; std::cout << "10 - 5 = " << calculator.subtract(10, 5) << std::endl;
std::vector<int> numbers = {3, 7, 2, 9, 1}; std::cout << "Max: " << templates::max_element(numbers) << std::endl; std::cout << "Min: " << templates::min_element(numbers) << std::endl;
return 0; }
|
app/CMakeLists.txt:
1 2 3 4 5 6
| add_executable(app main.cpp)
target_link_libraries(app PRIVATE calc array_utils )
|
根 CMakeLists.txt:
1 2 3 4 5 6
| cmake_minimum_required(VERSION 3.15) project(CrossPlatformLib VERSION 1.0.0)
add_subdirectory(shared_lib) add_subdirectory(header_only_lib) add_subdirectory(app)
|
关键点:
- 动态库使用
GenerateExportHeader 自动生成跨平台导出宏 - 仅头文件库使用
INTERFACE 类型,不编译任何源文件 INTERFACE 库的所有属性都使用 INTERFACE 关键字
集成第三方库
场景:创建一个使用 Boost 和多线程的网络应用。
项目结构:
1 2 3 4 5 6 7 8 9 10
| network_app/ ├── CMakeLists.txt ├── include/ │ └── network/ │ ├── tcp_client.h │ └── tcp_server.h └── src/ ├── tcp_client.cpp ├── tcp_server.cpp └── main.cpp
|
include/network/tcp_server.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #pragma once #include <boost/asio.hpp> #include <memory> #include <string>
namespace network { class TcpServer { public: TcpServer(unsigned short port); void start(); void stop(); private: boost::asio::io_context io_context_; unsigned short port_; }; }
|
src/tcp_server.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include "network/tcp_server.h" #include <iostream>
namespace network { TcpServer::TcpServer(unsigned short port) : port_(port) {}
void TcpServer::start() { std::cout << "Server starting on port " << port_ << std::endl; }
void TcpServer::stop() { std::cout << "Server stopped" << std::endl; } }
|
src/main.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include "network/tcp_server.h" #include <iostream>
int main() { try { network::TcpServer server(8080); server.start(); server.stop(); } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; return 1; } return 0; }
|
CMakeLists.txt:
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
| cmake_minimum_required(VERSION 3.15) project(NetworkApp VERSION 1.0.0)
find_package(Threads REQUIRED) find_package(Boost 1.70 REQUIRED COMPONENTS system thread)
add_library(network_lib src/tcp_server.cpp )
target_include_directories(network_lib PUBLIC ${PROJECT_SOURCE_DIR}/include )
target_link_libraries(network_lib PUBLIC Boost::system Boost::thread PRIVATE Threads::Threads )
target_compile_features(network_lib PUBLIC cxx_std_17)
add_executable(network_app src/main.cpp) target_link_libraries(network_app PRIVATE network_lib)
|
说明:
- 使用
find_package 查找 Threads 和 Boost 库 - Boost 库使用
PUBLIC,因为 boost::asio 类型出现在头文件中 - Threads 使用
PRIVATE,仅在实现中使用 - 现代 CMake 使用
Boost::system 而不是 ${Boost_LIBRARIES}
代码复用技巧
自定义函数:封装常用操作
将重复的 CMake 代码封装成函数,可以提高代码复用性和可维护性。
场景:每次创建库都要设置相同的编译选项、警告级别、C++ 标准等,可以封装成函数。
在根 CMakeLists.txt 中定义函数:
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
| function(add_my_library lib_name) set(options SHARED STATIC) set(multiValueArgs SOURCES LIBS) cmake_parse_arguments(ARG "${options}" "" "${multiValueArgs}" ${ARGN})
if(ARG_SHARED) set(lib_type SHARED) else() set(lib_type STATIC) endif()
add_library(${lib_name} ${lib_type} ${ARG_SOURCES})
target_include_directories(${lib_name} PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include> PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src )
target_compile_features(${lib_name} PUBLIC cxx_std_17)
if(MSVC) target_compile_options(${lib_name} PRIVATE /W4) else() target_compile_options(${lib_name} PRIVATE -Wall -Wextra -Wpedantic) endif()
if(ARG_LIBS) target_link_libraries(${lib_name} PUBLIC ${ARG_LIBS}) endif() endfunction()
|
使用自定义函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| add_my_library(mathlib SOURCES src/add.cpp src/subtract.cpp LIBS m )
add_my_library(utils SHARED SOURCES src/string_util.cpp src/file_util.cpp LIBS Boost::filesystem )
|
优点:
💡 提示:更复杂的模块化设计(如独立的 .cmake 模块文件、find_package 配置文件)在大型项目中才会用到,这里不展开讲解。
项目组织建议
小型项目(单个库)
1 2 3 4 5 6 7 8
| project/ ├── CMakeLists.txt ├── include/mylib/ │ └── mylib.h ├── src/ │ └── mylib.cpp └── examples/ └── example.cpp
|
中型项目(多个库)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| project/ ├── CMakeLists.txt ├── cmake/ # CMake 模块 │ └── MyFunctions.cmake ├── libs/ │ ├── core/ │ │ ├── CMakeLists.txt │ │ ├── include/core/ │ │ └── src/ │ └── utils/ │ ├── CMakeLists.txt │ ├── include/utils/ │ └── src/ └── apps/ └── myapp/ ├── CMakeLists.txt └── main.cpp
|
大型项目(多模块)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| project/ ├── CMakeLists.txt ├── cmake/ │ ├── Config.cmake.in │ └── FindXXX.cmake ├── modules/ │ ├── module1/ │ │ ├── CMakeLists.txt │ │ ├── include/ │ │ ├── src/ │ │ └── tests/ │ └── module2/ │ └── ... ├── external/ # 第三方库 └── docs/
|
参考资源