c++oding="utf-8" ?>
std::source_location 必须在调用现场用 ::current() 获取,因默认参数在声明处求值导致位置固定;日志宏需用 do { ... } while(0) 封装并显式传入 current(),以确保捕获真实调用点,零运行时开销但略增二进制体积。

std::source_location 不能直接用于日志宏之外的普通函数参数传递来获取调用点信息——它默认捕获的是定义处(而非调用处)的位置,除非你显式用 std::source_location::current() 在调用现场构造。
为什么日志宏里必须用 std::source_location::current() 而不是传参?
因为 std::source_location 是一个普通结构体,如果写成 void log(const char* msg, std::source_location loc = std::source_location::current()),这个默认参数是在函数声明处求值的——也就是编译器在解析函数签名时就固定了位置,实际调用时不会更新。
常见错误现象:所有日志都显示同一行(比如 log 函数定义的那一行),文件名也固定为头文件路径。
正确做法是把位置信息“推到调用侧”:
- 用宏包裹日志调用,宏内展开
std::source_location::current() - 宏展开发生在预处理阶段,此时上下文就是真实调用点
- 避免任何中间函数透传,否则会丢失原始调用位置
如何写一个安全、可移植的日志宏(支持 C++20)?
需要兼顾:不污染命名空间、不引发二义性、兼容不同编译器对 std::source_location 的实现差异(如 GCC 12+ 和 Clang 14+ 行为一致,但 MSVC 早期版本有 bug)。
实操建议:
- 宏名避免和用户变量冲突,例如用
LOG_INFO而非log - 使用
do { ... } while(0)包裹多语句逻辑,防止 if-else 分支误挂 - 显式传递
std::source_location::current(),不依赖默认参数 - 字段提取用
loc.file_name()、loc.line()、loc.function_name(),注意file_name()返回const char*,可能含完整路径,需自行截取文件名
示例宏片段:
#define LOG_INFO(msg) \
do { \
::my_logger::log_impl(msg, std::source_location::current()); \
} while(0)
std::source_location 的性能开销与编译期行为
它不涉及运行时堆分配或系统调用,所有字段(文件名、行号、函数名)都是编译器在生成代码时硬编码进二进制的字符串字面量和整数字面量。
这意味着:
- 零运行时开销:没有函数调用、无字符串拷贝(除非你主动调用
std::string(file_name())) - 但会略微增大目标文件体积(每个唯一调用点存一份路径字符串)
- 启用
-g或-O2不影响其可用性;但若关闭调试信息(如-g0),file_name()仍有效,因为它是源码层面的字面量,不是 DWARF 符号 - 注意:某些嵌入式工具链或旧标准库(如 libstdc++ __cpp_lib_source_location 宏
容易被忽略的兼容性陷阱
最常踩的坑不是语法,而是构建环境与标准库版本错配:
- 即使编译器支持 C++20(如 GCC 11),libstdc++ 可能未导出
std::source_location—— 需升级到 libstdc++ 12+ 或改用 libc++ - Clang + libstdc++ 组合下,
function_name()可能返回空字符串,因 ABI 层未填充该字段 - Windows 上 MSVC 19.30+ 才稳定支持,早期 preview 版本返回的
file_name()带冗余驱动器前缀(如C:/a/b/file.cpp),而 Linux 下通常为相对路径 - CI 环境中若混用容器镜像(如 ubuntu:20.04 默认带 GCC 9),即使加了
-std=c++20也会静默失败——建议在 CMake 中显式检查:check_cxx_source_compiles("...std::source_location::current()..." HAVE_SOURCE_LOCATION)
真正难的从来不是怎么写,而是怎么让同一套宏在本地开发、CI 构建、客户交付环境里都返回一致且可信的位置信息。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











