如果你之前在 Linux 上用过 spdlog,觉得它“下载即用、include 就能跑”,那到 Windows 上第一次编译可能就会被 CMake 的生成器选项、运行时库、Debug/Release 配置啪啪打脸。这个库 2026 年在 C++ 项目里依然是日志方案的第一梯队:单头文件、高性能、异步队列、多 sink,你在 Windows 上把它编译通过,后面基本一劳永逸。这篇教程不像官方 README 那样丢几条命令就完事,我会从环境准备、三种编译方式、工程集成到 Windows 专属的踩坑实录,按我实际操作的顺序完整走一遍,目标是让没用过 CMake 的纯新手也能一次跑通,顺便把《C++ 编译原理》里那些“为什么这么做”也讲明白。
1. 为什么要在 Windows 上手动编译 spdlog,而不是直接复制头文件
1.1 spdlog 到底是什么,为什么 C++ 项目里它是“标配”
spdlog 是 Gabi Melman 开源的 C++ 日志库,核心卖点就三个:快、简单、可扩展。它基于 fmt 库做格式化输出,底层用了大量模板和编译期优化,所以常规日志打印的性能比printf那一类实现高出一大截,这也让它成了很多后端服务、游戏客户端、工具链项目的默认选择。
对于 C++ 开发者来说,日志库本质上就是一个“背锅观察窗口”:程序崩了、请求慢了、数据处理不对了,你总得有个地方记录现场。spdlog 提供 logger 注册表、全局默认 logger、异步线程池,还有控制台、文件、rotating file 这类开箱即用的 sink,你在业务代码里只要写一行spdlog::info("order id: {}", id);,剩下的事情它自己处理。
但问题也出在这里:spdlog 虽然提供了 header-only 模式,但在 Windows 上做正式项目时,我强烈建议你把它编译成静态库或动态库接入。原因后面会细说,简单提一句:header-only 会让每个包含它的 .cpp 文件重复实例化大量模板代码,编译时间直线上升,而且多个编译单元之间如果都持有内部静态状态,日志 logger 的全局注册表会出现“这个 .cpp 里注册的,另一个 .cpp 里找不到”这种诡异问题。
1.2 header-only / 静态库 / 动态库怎么选,这是每次都要回答的问题
很多新手看到 spdlog 文档里写“你只需要复制 include 目录”,就真的只用头文件了。小项目这么做没问题,但你要分清楚三种模式的应用场景。
第一种是SPDLOG_HEADER_ONLY模式。你只需要把 spdlog 的include/spdlog目录丢进工程,然后在编译器里定义宏,它就能跑。这个模式对单文件 demo、临时测试脚本、快速验证逻辑很友好,因为它零编译配置、零链接负担。代价是每个包含它的编译单元都会生成一套模板实例,编译慢,最终二进制体积也会变大。
第二种是编译成静态库,这部分是官方推荐的正式项目接入方式。用 CMake 把 spdlog 源码编译成spdlog.lib,工程里只链接这个 lib,头文件路径仍然指向include目录。此时日志注册表、线程池、异步队列这些共享状态由库本身托管,多个编译单元不会各自为政。大多数服务端项目、桌面工具我都推荐这种方式。
第三种是编译成动态库,Windows 上就是生成spdlog.dll+spdlog.lib导入库。它适合“多模块共享同一个日志实例”的插件化架构,缺点是 DLL 的导出符号和运行时库版本匹配问题在 Windows 上特别考验耐心,能用静态库解决的,不建议轻易上 DLL。
所以在本文接下来的步骤里,默认目标是:用 Visual Studio 2022 + CMake,把 spdlog 编译成 x64 Release 静态库,然后通过 CMake find_package 集成到自己的项目。这个流程最稳,也最接近真实工程的构建方式。
2. 编译前要准备的 Windows 环境与工具链
2.1 Visual Studio 的 C++ 工作负载怎么装,别装了个空壳
在 Windows 上编译 C++ 库,绕不开微软的 MSVC 编译器。你如果只装了 Visual Studio Installer 里的“.NET 桌面开发”工作负载,那大概率是没有 C++ 编译器的。我遇到过太多人打开 VS 说“我怎么没法编译 C++”,原因就是安装时没勾选组件。
正确的安装路径是:打开 Visual Studio Installer,找到你装的 VS 2022(没有就先装 Community 版,个人和小团队免费),点“修改”,在“工作负载”标签页勾选“使用 C++ 的桌面开发”。右边默认的组件里确保有 MSVC v143 生成工具、Windows 10/11 SDK、C++ CMake 工具这三个,缺哪个补哪个。
这里“为什么”值得讲一下:CMake 只是个构建系统生成器,它本身不编译代码,实际编译动作全部交给 MSVC 编译器。MSVC 工具链和 Windows SDK 是配套的,SDK 提供头文件和系统导入库,MSVC 提供cl.exe、link.exe。你如果装的是精简版 VS 或者只装了 Build Tools,命令行编译和 CMake 生成也能跑,但在 VS 里调试时会少不少体验,所以我建议直接装完整桌面工作负载。
装完之后建议顺手把“Windows 10 SDK”版本选新一些,spdlog 本身对 SDK 版本要求不高,但新 SDK 对 C++ 标准库和 std::thread 等组件的支持更完善,能少碰一些莫名其妙的系统头文件报错。
2.2 CMake 的安装,以及“你用的是哪套 CMake”
2026 年还靠手写 .vcxproj 维护 C++ 工程的团队基本绝迹了,CMake 已经是事实标准。你只需要从 cmake.org 下载 Windows x64 安装包,安装时勾选“Add CMake to the system PATH for all users”,这样在任意终端里都能直接敲cmake命令。
装完验证一下:打开 PowerShell 或 CMD,输入cmake --version,能看到版本号就行。如果提示找不到命令,多半是没加入 PATH,重装一次或者在“系统环境变量”里手动把 CMake 的 bin 目录加上去。
这里有个 Windows 上的坑:Visual Studio 其实也自带一套 CMake,位置一般在C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe。如果你在“开始菜单”里打开“Developer Command Prompt”并直接敲 cmake,用的可能是 VS 自带的那套,版本通常比官网落后一年左右。
spdlog 对 CMake 的最低版本要求不高,但为了省事,我建议统一用官网安装的新版 CMake,并且在命令行里用cmake --version确认一次,避免后续出现“版本太低导致某选项不识别”的困惑。
2.3 下载 spdlog 源码:记得看 tag,别拉 master 盲等
这一步很简单,但很多人会搞混。spdlog 的官网仓库地址是https://github.com/gabime/spdlog,你不要直接下载 master 分支的 zip 包就开搞。master 分支时刻在变,今天能编译,明天可能因为 fmt 版本调整就出幺蛾子。我建议在 Releases 页面选择一个稳定 tag,比如 v1.14.x 或 v1.15.x,下载对应的 Source code 压缩包。
下载后解压到一个干净目录,比如D:\thirdparty\spdlog-1.15.x。注意路径里不要带空格和中文,MSVC 和 CMake 对特殊字符路径的容忍度比 Linux 差不少。解压完成后你应当能看到include/、src/、CMakeLists.txt这几个关键部分。include/spdlog/spdlog.h是主头文件,src/spdlog.cpp是编译库时需要包含的源码入口之一,CMakeLists.txt 则描述了整个构建流程。
顺带提一个面试常考的“编译原理”基础:spdlog 作为模板库,大部分实现都堆在头文件里,但它的src/目录里那些 .cpp 文件,比如spdlog.cpp、async.cpp、fmt.cpp,实际上是在实例化模板并导出符号,这样你在自己的代码里调用spdlog::info、basic_logger_mt时,链接器能在库中找到对应实现,而不是让所有工程重新推导一遍模板。这就是为什么我前面建议“编译成库而不是纯 header-only”——编译原理上这叫显式模板实例化与封装,能大幅压缩整体编译时间。
3. Windows 下编译 spdlog 的三条可行路线
3.1 零编译接入:SPDLOG_HEADER_ONLY 直接 include,适合快速验证
如果你现在只想在 5 分钟内跑个 demo,可以完全不编译库,直接把解压后的include目录配给你的工程,并在预处理器定义里加上SPDLOG_HEADER_ONLY。以 Visual Studio 举例:右键项目 → 属性 → C/C++ → 常规 → 附加包含目录,把include填进去;再到 C/C++ → 预处理器 → 预处理器定义里添加SPDLOG_HEADER_ONLY。
然后写一段验证代码:
#include <spdlog/spdlog.h> int main() { spdlog::info("header only mode works, version: {}.{}", SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR); return 0; }这里SPDLOG_VER_MAJOR和SPDLOG_VER_MINOR是 spdlog 提供的版本宏,编译期展开成数字,既方便你确认头文件版本,也能验证 include 路径对不对。如果头文件路径没配好,编译器会直接报“无法打开包含文件 spdlog/spdlog.h”。
这个方案最大的优点是零依赖,不用碰 CMake。缺点是只适合“尝鲜”级别:一旦你的项目有多个 .cpp 文件都用到 spdlog,或者你想用异步日志、自定义 sink、多 logger 注册表,header-only 模式就会开始在链接阶段给你上眼药。所以商业项目、课程大作业、长期维护的代码库,还是走下面两条路。
3.2 CMake GUI + Visual Studio 生成器:小白最稳的一条路
如果你不想背命令行参数,还想看看 CMake 的生成本质,那就用 CMake GUI 配置一次。打开 CMake GUI 后,第一行“Where is the source code”填 spdlog 解压目录,第二行“Where to build the binaries”填一个 build 目录,比如D:\thirdparty\spdlog-build。点 Configure 时会弹窗让你选生成器,这里选“Visual Studio 17 2022”,右侧平台选 x64,如果你机器上装了 VS 2026 之类的新版本,选对应的生成器即可,流程一样。
第一次 Configure 之后,界面下方会出现一堆红色项和变量。这里有几个我们必须调整的核心选项,每一个我都解释下为什么要这么改:
SPDLOG_BUILD_SHARED:控制编译动态库还是静态库。默认是 OFF,即静态库,保持默认。这个选项名很容易劝退新手,因为名字带 SHARED,让你以为“共享”就是“更好”,但这里其实是“shared library(动态库)”的意思。普通项目用静态库是默认最优解。SPDLOG_BUILD_EXAMPLE、SPDLOG_BUILD_TESTS、SPDLOG_BUILD_BENCH:这三个分别控制示例、测试、性能基准的编译。我们只是要库本体,全部取消勾选,否则 build 目录里会多出一堆不需要的项目,产出速度也会变慢。SPDLOG_ENABLE_INSTALL:是否生成 install 目标。建议勾上,这样你能在编译后用一条命令把 include 和 lib 统一拷到指定目录。SPDLOG_USE_STD_FORMAT:是否使用 C++20 的<format>替代内置 fmt。如果你用的是 C++17 项目,这里保持默认 OFF;如果整个项目已经切到 C++20/23,可以勾选它,减少一个 fmt 层,但我个人建议先别动,默认最省心。
确认完这些参数后,再次点 Configure,等红色项消失,再点 Generate。CMake 会在 build 目录里生成一个spdlog.sln解决方案文件。用 Visual Studio 打开这个 sln,把解决方案配置切换到 Release,右键 ALL_BUILD 项目,选择生成。编译完成后,你会看到spdlog.lib出现在build/Release目录里,同时include/spdlog一堆头文件还在源码目录里,等会要用的时候两边都要引用。
到这里你就完成了第一次 Windows 编译历程。整个过程其实就是在“让 CMake 了解你的编译环境和意图”,CMake 帮你把这些选择翻译成一个个 .vcxproj 工程文件,再由 VS 调用 MSVC 编译链接。
3.3 命令行 + vcpkg:自动化项目的最优解
如果你已经用 vcpkg 管理依赖,或者希望换台新机器一条命令就能把所有依赖库装好,那直接用 vcpkg 编译 spdlog 是效率最高的方式。先把 vcpkg clone 下来并执行 bootstrap:
git clone https://github.com/microsoft/vcpkg cd vcpkg .\bootstrap-vcpkg.bat .\vcpkg install spdlog:x64-windows这里:x64-windows指定的是 vcpkg 的 triplet,意思是“用 x64 架构、静态链接系统库的 Windows 版本”。如果你不确定 vcpkg 装出来的库到底是动态还是静态,可以加一个--dry-run参数先试一遍,它会打印即将构建的内容和依赖关系,不会真的动手。
vcpkg 装完 spdlog 后,会打印一段提示,包含find_package(spdlog CONFIG REQUIRED)的命令示例。你只需要在自己的 CMakeLists.txt 里指定工具链文件即可,后续我把完整写法放到第 4 节讲。
这里提醒一下:vcpkg 默认构建会采用它自己的一套运行时库策略,如果你的主工程用官方 CMake 但没走 vcpkg 工具链,两边可能因为运行时库不一致产生链接错误。所以用 vcpkg 就别混合“手动 CMake 配置”,要么全程走 vcpkg,要么全程不用。
如果你不用 vcpkg,也可以直接开命令行按照官方 CMake 流程编译,命令如下:
cmake -S D:\thirdparty\spdlog-1.15.x -B D:\thirdparty\spdlog-build ` -G "Visual Studio 17 2022" -A x64 ` -DSPDLOG_BUILD_SHARED=OFF ` -DSPDLOG_BUILD_EXAMPLE=OFF ` -DSPDLOG_BUILD_TESTS=OFF ` -DSPDLOG_BUILD_BENCH=OFF ` -DSPDLOG_ENABLE_INSTALL=ON cmake --build D:\thirdparty\spdlog-build --config Release --parallel 8 cmake --install D:\thirdparty\spdlog-build --config Release --prefix D:\thirdparty\spdlog-install这三条命令的原理和 CMake GUI 操作完全一致,只不过参数显式写了出来。第二条--build的--config Release指定多配置生成器的编译模式,因为 VS 生成的 .sln 同时包含 Debug 和 Release,你得告诉编译器用哪个。第三条--install的--prefix是安装根目录,执行完之后D:\thirdparty\spdlog-install下会有一个干净的 include 和 lib,这对后面集成进大项目或者做 CI 特别友好。
4. 把编译好的 spdlog 接到自己的 C++ 工程里
4.1 CMakeLists.txt 的 find_package + 链接写法
假设你已经把 spdlog install 到了D:\thirdparty\spdlog-install,现在要把写好的 main.cpp 和它链接起来。打开你自己项目的 CMakeLists.txt,核心配置长这样:
cmake_minimum_required(VERSION 3.25) project(MyApp LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 告诉 CMake 去哪找 spdlogConfig.cmake set(CMAKE_PREFIX_PATH "D:/thirdparty/spdlog-install") find_package(spdlog CONFIG REQUIRED) add_executable(MyApp main.cpp) target_link_libraries(MyApp PRIVATE spdlog::spdlog)这里有两个值得注意的“为什么”。set(CMAKE_PREFIX_PATH ...)是为了让find_package在默认搜索路径之外多找一条路,如果你 install 时用了--prefix,就必须靠这个变量把安装目录暴露给 CMake。spdlog::spdlog是 spdlog 导出目标的名字,它会自动带上头文件目录和spdlog.lib的路径,不需要你手写 include_directories 和 link_directories,这也是现代 CMake 推荐的做法。
如果你走的是 vcpkg 路线,不需要手动set(CMAKE_PREFIX_PATH),而是要在配置工程时传入 vcpkg 的工具链文件:
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=D:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake这样 vcpkg 会自动把spdlog的包加入 CMake 包搜索路径,find_package(spdlog CONFIG REQUIRED)依旧生效,整个逻辑是通顺的。
4.2 第一段 spdlog 代码:控制台加文件双输出,顺带跑一下异步
编译库的最终目的就是让这些高级功能可以稳定使用。下面这段代码包含控制台输出、文件 sink 和异步 logger,正好是日常项目里最高频的组合:
#include <spdlog/spdlog.h> #include <spdlog/sinks/basic_file_sink.h> #include <spdlog/async.h> int main() { // 1. 默认全局 logger,输出到控制台 spdlog::info("app start, version: {}.{}", SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR); // 2. 创建写日志文件的 logger,自动建 logs 目录 auto fileLogger = spdlog::basic_logger_mt("file_logger", "logs/app.log"); fileLogger->set_level(spdlog::level::debug); fileLogger->debug("debug message: value = {}", 42); // 3. 创建异步 logger,队列 8192 条,用满 4 个后台线程 auto asyncLogger = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>( "async_logger", "logs/async.log"); asyncLogger->info("async logging works"); spdlog::drop_all(); return 0; }这段代码里有几个必须理解的细节。basic_logger_mt的_mt是多线程安全的标识,单线程场景可以用_st。drop_all()在程序退出前会统一销毁所有注册过的 logger,确保文件缓冲区的数据刷到磁盘,你要是漏了这步,log 文件很可能是空的,或者只写了最后几行——这是新手最容易怀疑“文件日志丢失”的原因,其实只是缓冲没刷。
异步 logger 的实现上也值得多说一句:create_async会把日志投递到内部队列,由后台线程池统一消费。这也是我反复强调“要编译成库再用异步”的原因之一,因为 spdlog 的异步线程池需要跨编译单元共享同一个队列和线程状态,纯 header-only 模式下多个 .cpp 文件很容易各自弄出一份线程池实例,最后你的异步日志就乱了套。
编译并运行后,控制台会出现一条 info,项目根目录下会生成logs/app.log和logs/async.log两个文件,里面的内容分别来自同步和异步 logger。看到这个结果,就说明你的 spdlog 库已经完全可用。
4.3 在 VSCode 里配置 includePath 和调试符号
Visual Studio 之外,现在很多人写 C++ 用的是 VSCode + C/C++ 插件 + CMake Tools 插件。VSCode 和 VS 的配置逻辑不太一样,它不为你的 .vcxproj 负责,而是靠c_cpp_properties.json和tasks.json分别管“智能提示”和“编译任务”。
先说智能提示。在项目根目录创建.vscode/c_cpp_properties.json,把编译好的 spdlog 头文件目录填进 includePath:
{ "configurations": [ { "name": "Win64", "includePath": [ "${workspaceFolder}/**", "D:/thirdparty/spdlog-install/include" ], "defines": [], "compilerPath": "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.4x.xxxxx/bin/Hostx64/x64/cl.exe", "cStandard": "c17", "cppStandard": "c++17" } ], "version": 4 }这里的 compilerPath 路径里的版本号因人而异,你可以通过where cl在开发者命令行里找到。如果你不想手写这个文件,也可以直接在 VSCode 里按下Ctrl+Shift+P,运行“C/C++: Edit Configurations (UI)”,图形化添加 include 路径,最终效果一样。
然后是调试问题。如果你用 CMake Tools 插件构建,它会自动生成编译和调试配置。调试时建议在 CMakeLists.txt 里开启 Debug 信息:
set(CMAKE_BUILD_TYPE Debug)同时确保你编译 spdlog 时也生成了 Debug 版本的库,否则你会遇到“源码里下断了,但库内部刷不出变量”的尴尬。最简单的方式是回到 3.2 节,把构建模式切到 Debug 再编译一遍,把 Debug 库放到另外一个目录,发布时用 Release 库,Debug 时用 Debug 库,这是 Windows 上最稳妥的双配置方案。
5. Windows 专属排错实战:这些坑我全踩过
5.1 链接错误速查表
spdlog 本身代码质量很高,所以你在 Windows 上踩的坑绝大多数是环境问题,不是它的问题。我把这些年看过的高频错误整理成表,方便你对症下药:
| 报错信息 | 出现原因 | 解决办法 |
|---|---|---|
| LNK2038 mismatch detected for 'RuntimeLibrary' | 主工程的运行时库和 spdlog 库不一致,一个用 /MD,一个用 /MT | 在 Visual Studio 项目属性中把“运行库”统一为“多线程 DLL(/MD)”或“多线程调试 DLL(/MDd)” |
| fatal error C1083: Cannot open include file: 'spdlog/spdlog.h' | include 路径没配置或路径配错 | 确认 includePath 及 C/C++ 附加包含目录正确指向include目录 |
| LNK1104 cannot open file 'spdlog.lib' | 链接器找不到库文件 | 链接器 → 附加依赖项里添加 spdlog.lib,并设置附加库目录 |
| C4996 'localtime': This function or variable may be unsafe | Windows CRT 的安全告警 | 在预处理定义里加_CRT_SECURE_NO_WARNINGS,或者在代码开头#define _CRT_SECURE_NO_WARNINGS |
| error C2039: 'get' is not a member of 'std::...' | C++ 标准版本太低,或头文件顺序问题 | 检查 CMAKE_CXX_STANDARD 是否 >= 17,并确保在包含 spdlog 之前定义了正确的项目标准 |
| 链接时大量“重复定义” | 主工程同时把 spdlog 源码 .cpp 也编进了项目,又链接了 spdlog.lib | 不要手动把src/目录的文件加进工程,只链接预编译好的库 |
其中 LNK2038 是最经典、也最容易忽悠人的天坑。它的本质是 MSVC 在链接时会检查目标文件和主程序里的运行时库宏,Debug 和 Release、静态运行时和动态运行时稍有错位就直接拒绝对接。你如果明明生成了 .lib,却一直提示不匹配,先检查 spdlog 库是用 Release 还是 Debug 编的,再检查主工程当前是什么配置,多半就是 Debug 工程链接了 Release 库,或者反过来。
5.2 为什么日志漏行、不实时?缓冲与 sink 设置
很多时候你会遇到“程序正常退出,但日志文件里什么都没有”或者“程序运行中看日志文件,一直是空的”这类问题。第一反应别怀疑 spdlog 丢了数据,八成是缓冲没刷新。
spdlog 的回转文件 sink 和普通文件 sink 默认有自己的缓冲策略。你可以在退出前显式调用 flush:
fileLogger->flush(); spdlog::shutdown();spdlog::shutdown()是官方提供的一个收尾入口,它会等待异步线程处理完队列并刷新所有 sink。我一般在 main 函数最后调用它,而不是只调drop_all()。区别在于drop_all()销毁 logger 对象,shutdown()还会释放线程池和全局资源,多一层保险。
如果你想让日志“实时写盘”,可以在创建 sink 时设置 flush 间隔:
auto logger = spdlog::basic_logger_mt("file_logger", "logs/app.log"); logger->flush_on(spdlog::level::warn); // 或者 flush every 3秒 spdlog::flush_every(std::chrono::seconds(3));第一种是“到达某个日志级别立即刷盘”,适合关键业务;第二种是“每 3 秒统一刷一次”,适合流量大但不想频繁 io 的场景。你自己写业务日志时按需选一种,就不会再出现“退出程序才看到日志”的诡异体验了。
5.3 中文乱码与控制台编码
spdlog 默认按 UTF-8 处理字符串,而 Windows 控制台的默认代码页可能不是 UTF-8,导致你spdlog::info("你好,世界")出来一堆乱码。别慌,这是 Windows 老传统,不是你代码的问题。
有三种解法。第一种最简单,在程序入口处调用 Windows API 设置代码页:
#ifdef _WIN32 #include <windows.h> SetConsoleOutputCP(CP_UTF8); #endif第二种是运行前在终端执行chcp 65001,把当前控制台切到 UTF-8 代码页。这种方法对临时测试有效,但对用户不友好,因为你没法保证每个使用者都手动切。
第三种最通用,是设置自定义 pattern,明确输出格式,避免依赖终端自动识别:
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S] [%^%l%$] %v");%l 是日志级别,%v 是消息正文,%^ 和 %$ 用于给级别着色。这个 pattern 和乱码本身没有直接关系,但它能让日志结构清晰,乱码出现时也更容易判断是哪一段文本出了问题。如果你用文件 sink 记录日志,文件打开后建议用 UTF-8 编码打开,Visual Studio Code 和 Notepad 默认都支持,别用 Windows 自带的“记事本”旧版本,那个老版本默认 ANSI 保存,会二次踩坑。
我在实际项目里是这么处理的:所有写入日志的字符串在进入 spdlog 前统一转成 UTF-8,控制台 sink 在 Windows 下设置代码页,文件 sink 直接用 UTF-8 写。这样日志文件在任何平台打开都不会乱码,控制台上只要终端支持 UTF-8,也能正常显示。
5.4 编译慢、体积大?用编译库的方式换编译时间
很多人在 header-only 模式下越写越难受,因为项目里总有几十个文件要 include spdlog,每加一个 .cpp,编译就要重新解析一遍所有模板。我之前说过,编译成库就是解决这个问题的。在你把 spdlog 编成静态库之后,主工程的每个 .cpp 只编译自己所需要的那一小部分接口,库里的模板实例化早已完成,整体编译时间能下降一个量级。
如果项目足够大,还可以用预编译头把 spdlog 相关头文件全部塞进去,让大部分 .cpp 不再重复解析。Visual Studio 里就是“预编译头”设置,CMake 里可以配合target_precompile_headers来搞。但这一步属于后面性能优化的话题,新手先不用动,能稳定跑通编译和链接已经成功了一大半。
还有一点要提的是 Debug/Release 库的区分。你编译 spdlog 时如果只编了 Release 库,而主工程调试时用 Debug 配置,链接阶段会因 SANITIZE 设置和优化参数不匹配报一些奇奇怪怪的错。最简单的办法是按 4.3 节说的,Debug 和 Release 各编一份,名字不要混淆,我用spdlogd.lib表示 Debug 库,Release 库就叫spdlog.lib,在 CMake 里用生成器表达式区分:
target_link_libraries(MyApp PRIVATE $<$<CONFIG:Debug>:D:/thirdparty/spdlog-debug/lib/spdlogd.lib> $<$<CONFIG:Release>:D:/thirdparty/spdlog-release/lib/spdlog.lib> )这样配置好之后,主工程切 Debug 就自动用 Debug 库,切 Release 就用 Release 库,省得每次切换配置都要手工改路径。
结尾
从我第一次在 Windows 上编译 spdlog 到现在,这套流程基本没变过:拿到源码、配 CMake、选生成器、编 Release、找 lib、链接进主工程。真要说有什么心得,就是别在 header-only 和完整编译之间反复横跳。小 Demo 用 header-only 图个快,正式工程一开始就老老实实编译成静态库,后面写多线程、异步日志、多 sink 的时候才会顺心。如果你按这篇教程走完一遍,那你以后在 Windows 上编译 grpc、fmt、json 这类 CMake 工程都会轻松不少,因为底层逻辑完全一样。顺手把 spdlog 的 Debug 和 Release 两个版本的库都编好,存到一个固定的 thirdparty 目录里,留着以后所有项目复用,这才是最省时间的长久之计。