std::source_location是C++20引入的标准设施,用于编译期捕获调用点的文件名、函数名、行号等信息;它比__FILE__和__LINE__更类型安全、可传递、可默认构造,必须通过默认参数(如loc = std::source_location::current())隐式传入才能准确记录调用位置,手动调用current()会错误捕获定义点而非调用点。

std::source_location 是什么,为什么它能替代 __FILE__
std::source_location 是 C++20 引入的标准设施,用于在编译期捕获调用点的文件名、函数名、行号等信息。它比 __FILE__ 和 __LINE__ 宏更类型安全、可传递、可默认构造,且支持自定义日志函数签名——关键在于它**必须通过默认参数隐式传入**,不能手动构造(否则会丢失真实调用位置)。
常见错误是试图写 std::source_location::current() 并以为能拿到调用者位置:这是错的——它返回的是该语句所在位置,不是调用者位置。
- 正确方式:声明函数参数为
std::source_location loc = std::source_location::current() - 错误方式:
auto loc = std::source_location::current(); log("msg", loc);→ 总是记录 log 函数内部的位置 - 必须配合默认参数使用,编译器才能自动注入调用点信息
如何在日志函数中正确接入 source_location
最简可行的日志函数签名长这样:
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("user logged in"); → 输出类似 [main.cpp:42] user logged in。
立即学习“C++免费学习笔记(深入)”;
-
loc.file_name()返回const char*,注意不是std::string;部分编译器(如 GCC 13+)返回绝对路径,Clang 可能返回相对路径,取决于编译选项(如-frecord-command-line) - 若只需文件名不含路径,可用
std::filesystem::path(loc.file_name()).filename().c_str()(C++17 起),但会带来运行时开销 - 不要把
std::source_location存到类成员或跨线程传递——它只保证在当前栈帧有效,且不同编译器实现可能不兼容序列化
与宏封装共存时的陷阱
很多人想保留原有宏(比如 LOG("xxx")),又想用 std::source_location。直接套宏会失败:
#define LOG(msg) log(msg) // ❌ loc 默认值在宏展开处求值,不是调用处
正确解法是用带参数的宏,强制把 std::source_location::current() 放在调用点:
#define LOG(msg) log(msg, std::source_location::current())
但这丧失了默认参数的简洁性。更推荐的做法是彻底放弃宏,改用内联函数:
inline void LOG(const char* msg) {
log(msg); // 自动触发 default argument
}- 宏无法参与 SFINAE 或模板推导,而
std::source_location参数依赖编译器注入,宏展开破坏这一机制 - 如果必须兼容旧宏,检查预处理器是否已展开
__FILE__——一旦宏里用了__FILE__,就和std::source_location冗余,应择一弃用 - MSVC 对
std::source_location的默认参数支持从 19.30(VS 2022 17.0)起稳定,旧版本会静默退化为std::source_location::current()
文件名路径过长或显示不全怎么办
loc.file_name() 返回的路径长度没有标准限制,实际取决于编译器和构建配置。常见问题:输出一长串绝对路径(如 /home/user/project/src/util/log.cpp),日志太冗长。
- 最轻量处理:用
strrchr(loc.file_name(), '/')或strrchr(loc.file_name(), '\')找最后一个分隔符后截取(注意 Windows/Linux 差异) - 避免
std::string构造:日志函数常要求零分配,建议用const char*+ 偏移计算,例如const char* base = strrchr(loc.file_name(), '/') ?: loc.file_name(); printf("%s", base + 1); - CMake 中可通过
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -frecord-command-line")影响 GCC 的路径精度,但不一定缩短 - Clang 默认输出相对路径(相对于构建目录),GCC 默认绝对路径;统一行为需靠构建系统规范化源码树结构
真正麻烦的不是怎么截,而是不同编译器对 file_name() 的定义不一致——有的带扩展名,有的不带,有的含空格或特殊字符。生产环境建议先做一次 assert(std::isprint(*loc.file_name())) 验证,再决定是否清洗。


















