std::source_location::current() 返回调用点而非函数内部位置,因其记录表达式所在源码行;正确做法是将其作为默认参数置于函数末尾,由编译器在调用处自动填充。

std::source_location 不能自动记录日志位置——它必须显式传参,编译器不会帮你隐式注入。
为什么 std::source_location::current() 总是返回调用点而非日志函数内部
这是最常误解的一点:很多人以为在日志函数里直接写 std::source_location::current() 就能拿到 LOG_INFO("xxx") 的调用位置,结果总是固定显示日志函数自己的文件和行号。
原因很简单:std::source_location::current() 返回的是**该表达式所在源码位置**,而它写在日志函数体内,自然就指向函数定义处。
要拿到调用者的上下文,必须让调用方把位置信息“推”进来,而不是让被调用方“拉”。
立即学习“C++免费学习笔记(深入)”;
- 正确做法:把
std::source_location作为默认参数,由编译器在调用点自动填充 - 错误做法:在日志函数体里调用
std::source_location::current() - 注意:C++20 要求默认参数必须是字面量常量表达式,而
std::source_location::current()恰好满足这个条件
如何声明带自动位置的日志函数(C++20)
关键在于参数顺序和默认值写法。位置参数必须放在最后,并设为默认值 std::source_location::current(),否则无法省略传参。
void log_info(const char* msg,
const std::source_location loc = std::source_location::current()) {
fprintf(stderr, "[%s:%d] %s\n",
loc.file_name(), loc.line(), msg);
}调用时完全不用提位置参数:
log_info("connection established"); // 自动捕获这行的文件名与行号- 如果函数有多个重载,确保
std::source_location参数始终是最后一个,且有默认值 - 不要尝试用宏包裹这个函数再加位置参数——宏会破坏
current()的语义,让它指向宏展开处而非调用处 - 若需支持 C++17 或更早版本,只能靠宏 +
__FILE__/__LINE__模拟,但类型不安全、无列号、不可扩展
常见误用:试图在 lambda 或间接调用中“保留”位置信息
比如封装一个日志对象,想在构造时记下位置,后续调用都复用它——这行不通。
std::source_location 是一个轻量值类型,但它记录的是**构造它的那一行**。一旦你把它存进成员变量或传给另一个函数,它就固化了,不会随后续调用动态更新。
- 错误示例:
Logger logger{std::source_location::current()}; logger.write("msg");→ 所有日志都显示 Logger 构造那行的位置 - 正确思路:每次
write()都应接受新的std::source_location默认参数 - 若用可变参数模板封装,也要确保
std::source_location是函数模板的最后一个非包参数,并设默认值
兼容性与性能注意事项
std::source_location 是纯编译期机制,不涉及运行时堆分配或字符串拷贝,开销极小。但要注意几个现实约束:
- MSVC 19.30+、GCC 11+、Clang 12+ 才完整支持;低版本可能只支持部分字段(如缺失
function_name()) -
file_name()返回的是绝对路径还是相对路径,取决于编译器和构建系统(CMake 的set(CMAKE_CXX_STANDARD 20)不足以启用,还需确保/std:c++20或-std=c++20) - 调试符号未开启时,某些平台(如 stripped 的 Linux 二进制)可能返回空字符串或 "
" - 它不提供调用栈,只记录直接调用点;如需多层回溯,仍需
backtrace()或第三方库
真正容易被忽略的是:它依赖调用点是否在翻译单元内可见——头文件中内联定义的日志函数没问题,但如果日志实现在 .cpp 里且仅声明在头文件中,链接后位置信息可能丢失或不准。


















