序列化库集成:使用FetchContent下载nlohmann/json、protobuf、flatbuffers
说到序列化库,这几乎是每个C++项目都绕不开的话题。我个人习惯把序列化方案分成两类:一类是轻量级的JSON,适合配置文件、调试输出;另一类是高性能的二进制序列化,比如protobuf和flatbuffers,适合网络传输和持久化存储。今天我们就用FetchContent一口气把这三个库都集成进来。
核心思路:通过FetchContent声明三个依赖,让CMake自动下载并构建。你不需要手动去GitHub上克隆代码,也不用操心版本兼容问题。
为什么选这三个库?
我在项目中遇到过好几次这样的场景:调试阶段用JSON看数据,一目了然;上线后换成protobuf,省带宽、解析快;而flatbuffers呢,适合那些对延迟极度敏感的场景——比如游戏引擎里的属性同步。你想想看,一个项目里同时用三种序列化方案,其实挺常见的。
嗯,这里要注意:不是让你三个都上,而是给你一个模板。你只需要改一下库的名字和Git仓库地址,就能集成任意CMake支持的第三方库。
第一步:声明FetchContent
先看一个完整的CMakeLists.txt示例。我习惯把依赖声明放在一个单独的dependencies.cmake文件里,这样主CMake文件看起来清爽很多。不过今天为了演示,咱们直接写在主文件里。
cmake_minimum_required(VERSION 3.14)
project(SerializationDemo CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 开启FetchContent模块
include(FetchContent)
# ---------- nlohmann/json ----------
FetchContent_Declare(
nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.2
)
# ---------- protobuf ----------
FetchContent_Declare(
protobuf
GIT_REPOSITORY https://github.com/protocolbuffers/protobuf.git
GIT_TAG v21.12
)
# ---------- flatbuffers ----------
FetchContent_Declare(
flatbuffers
GIT_REPOSITORY https://github.com/google/flatbuffers.git
GIT_TAG v23.5.26
)
# 一次性下载并构建所有依赖
FetchContent_MakeAvailable(
nlohmann_json
protobuf
flatbuffers
)
# 添加我们的可执行文件
add_executable(demo main.cpp)
# 链接三个库
target_link_libraries(demo
PRIVATE
nlohmann_json::nlohmann_json
protobuf::libprotobuf
flatbuffers::flatbuffers
)
小提示:GIT_TAG 可以填版本号(如 v3.11.2),也可以填分支名(如 master)。但我建议你固定版本号,否则哪天上游更新了API,你的代码可能就编译不过了。我曾经吃过这个亏,半夜被CI构建失败的电话吵醒……
第二步:理解下载流程
你可能会问:这三个库加起来好几百兆,每次CMake都重新下载一遍?当然不是。FetchContent会把下载的源码缓存到build/_deps目录下。第一次下载后,只要你不删除build目录,后续构建都是增量编译。
说白了,它跟git clone + git submodule的思路类似,但更自动化。你想想看,如果项目有10个依赖,手动管理submodule得多痛苦?
注意:protobuf的构建比较特殊,它需要先编译protoc编译器。FetchContent会自动处理这个依赖链,但第一次构建会慢一些。建议用 cmake --build . --parallel 4 开启多核编译。
第三步:验证集成是否成功
写一个简单的main.cpp来测试。我习惯在每个库集成后都跑一个最小示例,确保链接没问题。
#include <iostream>
#include <nlohmann/json.hpp>
#include <flatbuffers/flatbuffers.h>
// protobuf的头文件需要先编译.proto文件,这里先不展开
// 后面章节会详细讲protobuf的.proto编译流程
int main() {
// 测试nlohmann/json
nlohmann::json j = {
{"name", "CMake课程"},
{"chapter", 26},
{"tags", {"序列化", "FetchContent"}}
};
std::cout << "JSON: " << j.dump(4) << std::endl;
// 测试flatbuffers(只验证链接,不实际构建schema)
flatbuffers::FlatBufferBuilder builder(1024);
std::cout << "FlatBuffers builder created, size: "
<< builder.GetSize() << std::endl;
std::cout << "All libraries linked successfully!" << std::endl;
return 0;
}
编译运行:
mkdir build && cd build
cmake ..
cmake --build . --parallel 4
./demo
如果看到输出,说明三个库都集成成功了。嗯,这里有个坑:protobuf的链接库名在不同版本可能不一样。我遇到过v3.21.0之后把libprotobuf改成了protobuf::libprotobuf,如果你用的版本比较老,可能需要调整target名称。
核心知识体系
下面这张图帮你理清FetchContent集成第三方库的完整流程:
常见问题与避坑
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 下载超时 | GitHub被墙或网络不稳定 | 设置 set(FETCHCONTENT_TIMEOUT 120) 或使用镜像仓库 |
| protobuf编译失败 | 缺少abseil依赖(新版本protobuf需要) | 在FetchContent_Declare之前先声明abseil,或者使用v3.21.0之前的版本 |
| flatbuffers找不到target | CMake版本低于3.14 | 升级CMake到3.14+,或者手动调用 add_subdirectory |
| 重复下载 | 每次clean build都重新clone | 设置 set(FETCHCONTENT_UPDATES_DISCONNECTED ON) 避免重复拉取 |
特别提醒:protobuf的构建会生成protoc可执行文件,这个文件在_deps/protobuf-build/目录下。如果你需要自定义protoc路径,可以用 set(Protobuf_PROTOC_EXECUTABLE ...) 指定。我曾经因为系统自带的protoc版本太老,折腾了半天才发现是路径问题。
进阶技巧:按需下载
有时候你只想下载某个库,不想三个一起下。可以用FetchContent_Populate代替FetchContent_MakeAvailable,这样你可以控制每个库的下载时机。
# 只下载nlohmann/json,不构建
FetchContent_GetProperties(nlohmann_json)
if(NOT nlohmann_json_POPULATED)
FetchContent_Populate(nlohmann_json)
add_subdirectory(${nlohmann_json_SOURCE_DIR} ${nlohmann_json_BINARY_DIR})
endif()
这个技巧在大型项目里很有用。比如你的项目有多个子模块,每个子模块只需要部分依赖,就可以按需加载,避免不必要的下载。
我的习惯:在项目根目录放一个 cmake/ 文件夹,里面按功能拆分依赖声明文件。比如 cmake/deps_serialization.cmake 专门管序列化库,cmake/deps_network.cmake 管网路库。这样主CMake文件只需要 include() 一下就行,清爽得很。
好了,序列化库的集成就讲到这里。你只要把上面的模板拿过去,改一下仓库地址和版本号,就能集成任意你需要的第三方库。下一节我们会深入protobuf的.proto文件编译流程,到时候再聊。
公众号:蓝海资料掘金营,微信deep3321