std::source_location::current() 是编译期生成的轻量结构体,直接嵌入调用点的文件名、行号、函数名和列号;不依赖调试信息或栈回溯,零运行时开销,但必须在原始调用处使用,封装后会丢失原始位置。

std::source_location::current() 是什么
它是一个编译期生成的、轻量级的结构体实例,用来自动捕获调用点的文件名、行号、函数名和列号 —— 不是靠运行时栈回溯,而是由编译器在调用 std::source_location::current() 的那一行直接“塞进去”的字面量信息。
关键在于:它不依赖调试符号、不触发栈遍历、没有运行时开销,但必须用在**直接调用处**;一旦被封装进普通函数再转发,拿到的就是那个封装函数的位置,不是原始调用者。
为什么不能直接传参给自定义日志函数
常见错误是写成这样:
void log(const char* msg, std::source_location loc = std::source_location::current()) {
printf("%s:%d %s\n", loc.file_name(), loc.line(), msg);
}然后调用:log("hello"); —— 这时 loc 记录的是 log 函数内部默认参数展开的位置,不是 log("hello") 那一行。
立即学习“C++免费学习笔记(深入)”;
解决办法只有一条:把默认参数逻辑上移到**每个实际调用点**,也就是用宏或模板推导。
- 宏最直接:
#define LOG(msg) do { ::log_impl(msg, std::source_location::current()); } while(0) - 函数模板 + 默认参数可行,但要求调用点必须显式触发模板推导(比如加
std::source_location{}或用auto参数),否则仍走默认值逻辑 - C++20 起支持
consteval函数内联生成source_location,但目前主流做法还是宏
std::source_location 在不同编译器下的行为差异
Clang 和 GCC 12+、MSVC 19.30+ 都支持,但细节有坑:
-
function_name()返回内容不稳定:Clang 返回"void foo()"形式,GCC 可能只返回"foo",MSVC 返回修饰后名字(如);别拿它做字符串匹配 -
file_name()可能是绝对路径、相对路径或仅文件名,取决于编译选项(如-frecord-gcc-switches或/FC);建议用std::filesystem::path(loc.file_name()).filename().string()提取干净文件名 - 某些旧版 MinGW 或嵌入式工具链可能完全不提供该特性,需用
__FILE__/__LINE__回退
性能和 ABI 兼容性要注意什么
std::source_location 本身只有 4 个 unsigned int 成员(通常 16 字节),按值传递完全没问题,但要注意两点:
- 不要把它塞进频繁调用的热路径里的类成员变量里——虽然小,但会增大对象尺寸,影响缓存局部性
- 如果动态库导出含
std::source_location参数的函数,不同编译器或 STL 版本之间可能 ABI 不兼容(尤其是function_name()的内存布局);跨 DLL 边界建议只传const char*和整数字段 - 启用 LTO(Link-Time Optimization)时,
current()的位置信息仍准确,因为它是编译单元粒度确定的,不是链接期决定的
最常被忽略的一点:它不能替代断点或 core dump 中的调用栈,只告诉你「这一行代码从哪来」,而不是「怎么执行到这来的」。真要追踪路径,还得靠 profiler 或 trace 工具。










