std::source_location::current() 不能直接用于日志宏内,因其在函数体内调用时记录的是函数定义位置而非调用位置;必须通过带默认参数的内联函数+宏转发,在调用点展开以准确捕获文件、行号等信息。

std::source_location::current() 为什么不能直接用在日志宏里?
因为 std::source_location::current() 是一个 constexpr 函数,它的调用点必须在编译期可确定——但如果你把它写在函数体内部(比如日志函数里),它记录的就是那个函数的定义位置,而不是调用它的那一行。你想要的是“谁调用了日志”,不是“日志函数自己在哪”。
常见错误现象:
所有日志都显示同一行(比如 logger.cpp:42),完全失去定位价值。
- 必须把
std::source_location::current()放在**调用点**,也就是宏展开时插入的位置 - C++20 要求编译器支持该特性,GCC 10+、Clang 11+、MSVC 19.29+ 可用;老版本会编译失败或返回空信息
- 不能作为普通函数参数传入(除非用默认参数 + 模板推导),否则调用点就丢失了
怎么用宏安全捕获调用位置?
最简可行方案是用带默认参数的内联函数配合宏,避免宏展开污染作用域或引发多次求值问题。不推荐纯宏实现(比如 #define LOG(x) do { ... } while(0) 里直接调用 current()),因为某些上下文(如 if 语句后无花括号)可能出错。
实操建议:
- 定义一个内联日志函数,最后一个参数为
std::source_location loc = std::source_location::current() - 用宏包裹该函数调用,宏本身只做一层转发,不添加逻辑
- 确保宏名不与用户变量冲突(比如加前缀
LOG_INFO而非log)
示例:
inline void log_impl(std::string_view msg, std::source_location loc = std::source_location::current()) {
fprintf(stderr, "[%s:%d] %.*s\n",
loc.file_name(), loc.line(),
static_cast<int>(msg.size()), msg.data());
}
#define LOG_INFO(msg) log_impl(msg)</int>
std::source_location 成员哪些字段真正可靠?
不同编译器对字段填充程度不一,别依赖所有字段都有效。实测中,file_name()、line()、column() 基本可用;function_name() 在 GCC/Clang 下常为空或简化为 auto,MSVC 相对完整但仍有截断风险。
-
file_name()返回 const char*,指向编译器内置字符串,生命周期贯穿程序运行,可直接用 -
line()和column()是 int,通常准确,但 column 在模板实例化深度大时可能不准 -
function_name()不应作为关键诊断依据——它不保证包含参数签名,也不保证跨平台一致 - 不要对
file_name()做路径裁剪假设(比如认为一定含/src/),实际可能是绝对路径、相对路径或仅文件名
性能开销和 release 构建要不要关掉?
零运行时开销:所有字段都是编译期填入的字面量地址和整数,std::source_location 对象本身不 new、不 copy、不调用任何函数。即使在 -O2 下,也只会生成几条 mov 指令加载地址和数字。
但要注意:
- 如果日志函数本身被内联,
source_location参数仍会正确反映原始调用点 - 若你用宏做了额外字符串拼接(比如把文件名和行号转成 std::string 再拼进消息),这部分才有开销——跟
source_location无关,是你的代码写的重了 - release 下无需 #ifdef 关闭,除非你连日志函数调用都想彻底剔除(那就得用宏控制整个 LOG_* 展开)
真正容易被忽略的是:一旦用了 std::source_location,你就不能再把日志函数声明为 [[noreturn]] 或强依赖尾调用优化——编译器需要保留调用帧来填写位置信息。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











