std::source_location::current() 必须在日志函数内部调用,因编译器会自动注入调用点位置;若提前存储则固定为声明处位置,且不可用于consteval或模板非类型参数。

std::source_location 不能替代日志库的上下文管理,但它能零开销、无侵入地补全 __FILE__ 和 __LINE__ —— 关键是必须用 std::source_location::current() 静态调用,否则拿到的是调用点而非调用者位置。
为什么 std::source_location::current() 必须写在日志函数内部
它不是“获取当前执行位置”,而是“编译器在调用该函数时,自动注入调用处的源码信息”。如果提前存成变量再传入,就固定成了那个变量声明的位置。
- 错误写法:
auto loc = std::source_location::current(); log("msg", loc);→loc记录的是这行本身的位置,不是log()被调用的地方 - 正确写法:日志函数签名带默认参数
std::source_location loc = std::source_location::current(),让编译器每次调用时现场生成 - 注意:C++20 要求编译器支持此特性,GCC 10+、Clang 11+、MSVC 19.30+ 可用;老版本会编译失败,不能靠宏 fallback 自动降级
如何封装一个带定位的调试宏(兼顾可读性与内联)
直接裸用 std::source_location 在函数里会阻碍内联,尤其当函数体稍大时。宏 + inline 函数组合更稳妥。
- 定义内联函数:
inline void debug_log(const char* msg, const std::source_location& loc = std::source_location::current()); - 配套宏:
#define DEBUG_LOG(msg) debug_log(msg)—— 宏展开后,std::source_location::current()才真正落在调用处 - 避免把
std::source_location存进日志结构体长期持有:它的file_name()返回const char*,指向只读段,但生命周期依赖编译器实现,不建议跨函数保存
和传统 __FILE__/__LINE__ 相比,哪些地方容易翻车
std::source_location 看似更“现代”,但行为差异藏在细节里:
-
file_name()返回完整路径(如"/home/user/proj/src/main.cpp"),而__FILE__可能是相对路径或宏展开结果,做日志裁剪时逻辑要重写 -
function_name()不保证可读:GCC 返回"void foo() [with T = int]"这类模板实例化名,Clang 更简洁,MSVC 常返回空字符串 —— 别依赖它做符号匹配 - 启用
-g或-Og不影响std::source_location,但关掉调试信息后,某些工具链可能无法解析出文件名(实际少见,但 CI 环境精简镜像中偶发)
性能几乎为零,但别在 hot path 上构造字符串
std::source_location 本身只是四个整数(line、col、file、func 的偏移),拷贝开销可忽略。真正拖慢的是后续处理:
- 不要在每帧循环里调用
loc.file_name()+std::string::find_last_of('/') + substr提取文件名 —— 改成一次预计算或静态缓存 - 避免在
std::format中直接拼接:std::format("{}:{} {}", loc.file_name(), loc.line(), msg)会触发多次小内存分配;高频日志建议用fmt::print或写死缓冲区 - 如果项目已用
spdlog,别自己封装 —— 它 1.12+ 版本原生支持source_loc参数,直接传SPDLOG_LOGGER_CALL宏即可
最常被忽略的一点:std::source_location::current() 是编译期确定的,但它的成员函数(如 file_name())返回的是运行时指针 —— 这意味着你不能在 consteval 函数里使用它,也不能用于模板非类型参数。想编译期断言文件名?还是老实用宏。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











