必须用函数默认参数+std::source_location::current()才能准确定位日志位置,手动传参或宏中误用会导致行号固定;正确做法是定义带默认值的参数并用宏封装实现。

直接用 std::source_location 做日志位置追踪,必须靠函数默认参数 + std::source_location::current(),其他写法基本都失效——不是记录错行号,就是固定在一个位置不动。
为什么手动传 std::source_location::current() 会失效
常见错误是写成:log("msg", std::source_location::current())。这看似合理,实则所有日志都会显示同一个文件名和行号(比如头文件里定义 log 函数的那一行)。
-
std::source_location::current()是consteval函数,编译期就求值,它返回的是“它被调用的那一行”的位置,不是“你写 log(...) 的那一行” - 一旦你在函数体、模板、静态初始化或宏里直接调用它,得到的就是那个上下文的位置,而非用户调用点
- MSVC/GCC/Clang 在这种误用下表现一致:日志全挤在一处,调试时完全无法定位真实出错位置
正确声明日志函数的三个硬性条件
只有同时满足这三条,std::source_location 才能真正捕获调用点:
- 参数必须带默认值,且默认值只能是
std::source_location::current()(不能加括号外的任何运算,比如std::source_location::current().line()就非法) - 参数类型推荐按值传递(
std::source_location loc = ...),它是 trivially copyable,无构造开销;不建议用const std::source_location&,C++ 不允许绑定到临时对象的非 const 引用 - 不能放在模板参数包末尾、变参函数中间或转发函数里——只要中间多一层调用,位置信息就变成那层的行号,不是原始调用点
宏封装是绕不开的现实方案
纯函数接口解决不了“用户调用 vs 日志实现”的位置偏移问题。想让 LOG("x = %d", x) 真正记录用户代码行,必须用宏:
立即学习“C++免费学习笔记(深入)”;
- 宏展开发生在预处理阶段,
std::source_location::current()在宏体内求值,自然落在用户源码行上 - 推荐写法:
#define LOG(msg) ::detail::log_impl((msg), std::source_location::current()),把实现藏在命名空间里,避免污染全局 - 若需格式化支持,宏里要先拼字符串再传入(
__VA_ARGS__和逗号会破坏参数解析,不能直接塞进std::source_location::current()调用) - 别在宏里做
std::string构造或路径截取——高频日志下隐式分配和重复计算是性能杀手
file_name() 返回路径太长怎么办
loc.file_name() 默认返回完整路径(如 /home/user/proj/src/module/log.cpp),直接打出来难读又占日志体积。
- 不要在日志函数内部做
std::string截取或strrchr查找——每次调用都触发堆分配或重复扫描 - 稳妥做法是封装一个
short_file_name(const char*)辅助函数,在调用点(即宏展开后)就完成裁剪,只保留最后两级目录+文件名 - 注意:
loc.file_name()返回的const char*生命周期绑定到编译单元,不能strdup或长期持有指针,裁剪结果也应是栈上临时字符串或只读视图
最易被忽略的一点:function_name() 在 GCC/Clang 下常为空或含完整签名,不可用于关键路径判断;而 file_name() 的路径格式依赖编译器和构建系统(CMake/Ninja/Bazel 行为不一),上线前务必在目标环境中实测输出。


















