c++oding="utf-8" ?>
std::source_location不能直接传参调用,必须作为带默认值的函数参数(= std::source_location::current())才能在调用点注入正确位置;否则因consteval特性导致所有日志显示同一位置。

std::source_location 能完全替代 __FILE__ 和 __LINE__ 宏,但必须用对方式——它不是宏,不能“传进去”,只能靠编译器在调用点自动注入。
为什么直接把 std::source_location::current() 当实参传会失效
常见错误是写成这样:
log("error"); // ✅ 正确:靠默认参数触发注入
log("error", std::source_location::current()); // ❌ 错误:所有日志都显示同一个位置
因为 std::source_location::current() 是 consteval 函数,它的返回值在编译期就确定了;一旦你在函数体里、类成员初始化列表里或全局作用域中调用它,得到的就是那个“定义点”的位置,而不是“调用点”的位置。
- 所有日志都显示构造函数所在行或头文件包含行
- 多处调用却只记录一个固定文件名和行号
- 调试时完全无法定位真实出错位置
std::source_location 必须作为带默认值的函数参数
正确姿势只有一种:声明函数时,把 std::source_location 设为带默认值的参数,且默认值只能是 std::source_location::current():
void log(const char* msg, const std::source_location& loc = std::source_location::current()) {
printf("[%s:%d] %s
", loc.file_name(), loc.line(), msg);
}
这时每次调用 log("xxx"),编译器会在该调用语句处生成对应的位置信息,填入 loc。
- 不能用
auto推导这个参数类型(C++ 不允许推导默认参数) - 不能省略
const &—— 值拷贝无意义,引用更轻量 - 不能把它塞进模板参数包或变参函数末尾(如
template<typename... args> void log(Args&&... args)</typename...>),否则无法保证注入时机
如何让日志里的文件名更简洁(不显示完整路径)
loc.file_name() 返回的是完整路径字符串(如 /home/user/proj/src/main.cpp),直接打出来不利于阅读。但别在日志函数里做 std::string 构造或 strrchr 截取——高频日志路径要避免隐式分配和重复计算。
稳妥做法是封装一个 short_file_name() 辅助函数,在调用点就完成裁剪:
const char* short_file_name(const char* full) {
#if defined(_WIN32)
const char* last = strrchr(full, '\');
#else
const char* last = strrchr(full, '/');
#endif
return last ? last + 1 : full;
}
<p>void log(const char* msg, const std::source_location& loc = std::source_location::current()) {
printf("[%s:%d] %s
", short_file_name(loc.file_name()), loc.line(), msg);
}</p>
- Windows 下需同时处理
'\'和'/',但多数现代编译器(MSVC/Clang/GCC)在 Windows 上也输出'/'分隔符,所以单用strrchr(full, '/')通常够用 - 不要用
std::filesystem::path——它会触发堆分配,且 C++17 起才有,和 C++20 的source_location并非强绑定 - 如果项目已用构建系统(如 CMake)定义了源码根目录,也可考虑编译期裁剪(通过宏 + 字符串字面量处理),但复杂度陡增,一般没必要
兼容性与性能上最容易被忽略的三点
它确实“零运行时开销”,但有三个实际影响点常被低估:
- GCC 10+、Clang 11+、MSVC 19.29+ 才支持;旧版本编译直接失败,别指望 -std=c++20 就能开箱即用
- 每个启用
source_location默认参数的函数,调用点会嵌入几个字节的常量数据(文件名地址、行号等),若函数未内联(比如跨 TU 或含复杂模板),这些数据会在多个目标文件里重复出现 - 调试符号体积会明显增大——编译器需保留更多源码映射关系,不影响运行速度,但
strip后的发布包 size 增长可忽略,debug包的 DWARF 信息会变大
真正要注意的,是别在每毫秒打 1000 条日志的热路径里无条件启用它;用编译开关(如 #ifdef DEBUG_LOG)包裹日志函数声明,才是工程实践中最务实的做法。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










