std::source_location必须作为带默认值的函数参数使用才能正确记录调用点位置;直接传std::source_location::current()会导致所有日志指向该调用行而非真实调用处。

直接说结论:std::source_location 能在日志中自动记录调用点位置,但**必须作为带默认值的函数参数使用**,否则所有日志都会显示同一个位置——通常是头文件包含行或函数定义行。
为什么std::source_location::current()不能直接当实参传
常见错误是写成 log("msg", std::source_location::current())。这会导致所有日志都指向 std::source_location::current() 这个调用本身的位置,而不是 log 的调用点。
-
std::source_location::current()是consteval函数,编译期就确定返回值,它“看到”的永远是自己被写下的那一行 - 在模板函数、内联函数或封装层里直接调用,会固定返回实例化点或头文件位置
- 调试时发现日志全挤在
log_impl.hpp:23,根本找不到真实出错的业务代码行
正确声明日志函数:默认参数是唯一可靠方式
只有让编译器在每次调用处自动注入,才能拿到真实位置。函数签名必须长这样:
void log(const char* msg, const std::source_location& loc = std::source_location::current());
- 必须用
const std::source_location&—— 值拷贝无意义,且source_location本身轻量(通常 4 个size_t) - 不能用
auto推导默认参数类型,C++ 不允许 - 不能塞进变参模板末尾(如
template),编译器无法保证注入时机void log(Args&&...) - 调用时写
log("timeout")即可,无需任何宏或额外参数
如何避免日志里出现完整路径(比如 /home/user/proj/src/core/db.cpp)
loc.file_name() 返回的是完整路径字符串,直接打印既难读又可能触发隐式 std::string 构造(高频日志要命)。
- 不要在日志函数内部做
strrchr或std::string::find_last_of—— 每次调用都重复计算 - 稳妥做法是在调用点就裁剪:写一个
short_file_name(const char*)辅助函数,在log入口处调用一次 - 更优解是把裁剪逻辑提前到宏里(如果用了宏封装),确保只算一次且不引入运行时开销
- 注意:
file_name()返回的const char*生命周期绑定编译单元,不能strdup或长期持有指针
宏封装仍是实际项目里的必要选择
纯函数调用虽简洁,但工业级日志常需控制级别(LOG_WARN / LOG_ERR)、条件编译(发布版禁用位置信息)、表达式求值保护等,这时宏绕不过去。
- 推荐模式:
#define LOG_ERR(msg) ::detail::log_impl((msg), std::source_location::current()) - 宏体里把
msg先存为const auto& _msg = (msg);,防止像LOG(x++)这种被多次求值 - 命名空间隔离(如
::detail::)避免符号污染 - 宏只是入口,真正逻辑仍在类型安全的函数里,兼顾了灵活性和可维护性
最易被忽略的一点:function_name() 返回值不可靠——GCC/Clang 可能返回修饰名(mangled name),MSVC 有时为空;别把它当断言或路由依据用。真要函数上下文,得结合调试符号或运行时栈解析工具。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











