跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

C++ std::source_location自动化日志追踪 _ 获取源码文件名行号【详解】

std::source_location 是 C++20 引入的安全替代 FILE 和 LINE 的类型,需通过 std::source_location::current() 显式获取调用点信息,不能默认构造;须配合默认参数函数与内联包装宏使用,支持编译期零开销但无法运行时关闭。 std::source_location 是什么,它能直接替代 __FILE__ 和 __LINE__ 吗? 能,但不是“直接替换”——
std::source_location
是 C++20 引入的类型,封装了调用点的文件名、函数名、行号、列号,比宏更安全、更可控。它不依赖预处理器展开,不会污染宏命名空间,也不会在内联或模板实例化时错位。 常见错误是以为它会自动捕获调用位置:其实它默认构造时只记录定义点(比如函数体内部),必须显式用
std::source_location::current()
才能获取调用者位置。 错误写法:
void log(const char* msg) { auto loc = std::source_location{}; /* 这里记录的是 log 函数自身位置 */ }
正确写法:
void log(const char* msg, std::source_location loc = std::source_location::current()) { /* loc 记录的是调用 log() 的那行代码位置 */ }
注意:参数必须设为默认值,且不能是 const 引用(否则无法绑定临时对象) 如何在日志宏里安全注入 std::source_location? 宏本身不能直接传入
std::source_location::current()
(C++20 允许,但部分老编译器不支持;且宏展开后可能被优化掉)。稳妥做法是用内联函数包装 + 宏转发。 典型结构: 立即学习 “ C++免费学习笔记(深入) ”;
inline void real_log(const char* msg, std::source_location loc = std::source_location::current()) { fprintf(stderr, "[%s:%d] %s ", loc.file_name(), loc.line(), msg); } #define LOG(msg) real_log(msg)
这样既避免宏参数求值顺序问题,又确保
std::source_location::current()
在宏调用点求值。 C知道 CSDN推出的一款AI技术问答工具 下载 不要写
#define LOG(msg) real_log(msg, std::source_location::current())
—— 某些 Clang 版本在 -O2 下可能把
current()
提前到宏定义处求值 如果需要格式化(如
LOG("x=%d", x)
),得用可变参数模板,而非可变参数宏(后者无法透传
std::source_location
) MSVC 19.30+、GCC 11+、Clang 12+ 支持良好;GCC 10 不支持
std::source_location
std::source_location::file_name() 返回的路径是绝对还是相对?怎么统一成文件名? 完全取决于编译器和构建系统——
file_name()
返回的是编译器传入的原始路径字符串,可能是绝对路径(如
/home/user/proj/src/log.cpp
),也可能是相对路径(如
src/log.cpp
),甚至带 . 前缀(Windows 上常见)。 没有标准库函数切分路径,必须自己处理。最轻量的做法是找最后一个
/
auto basename(const char* path) -> const char* { const char* p = strrchr(path, '/'); if (!p) p = strrchr(path, '\'); return p ? p + 1 : path; } // 使用:loc.file_name() → basename(loc.file_name())
别用
std::filesystem::path
—— 它会分配内存、抛异常,不适合高频日志路径提取 注意:某些构建系统(如 Bazel)可能生成带 execroot 的长路径,
strrchr
仍可靠 Clang 在 header unit 模式下可能返回空字符串,需判空 性能开销有多大?能不能关掉? 几乎为零:
std::source_location::current()
是编译期常量注入,不产生运行时函数调用。所有字段(
file_name()
,
line()
等)都是 constexpr 字符串字面量或整数字面量的引用,无内存分配、无字符串拷贝。 但它无法“关闭”——只要函数签名含默认
std::source_location
参数,编译器就必须在每次调用时填入该位置信息。真正控制开关得靠宏条件:
#ifdef ENABLE_SOURCE_LOCATION # define LOG(msg) real_log(msg) #else # define LOG(msg) real_log_no_loc(msg) #endif
Release 构建中若完全不需要源码信息,去掉默认参数比运行时判断更彻底 不要试图用
if constexpr
在函数体内跳过日志输出——位置信息已注入,省不掉 注意:即使不打印,
std::source_location
对象本身仍占 8~16 字节栈空间(取决于字段数量),对极端栈敏感场景(如中断 handler)要留意 最容易被忽略的一点:
std::source_location
function_name()
在 GCC 和 Clang 下返回的是 mangled 名,不是可读函数名;若需可读名,得额外接 cxxabi 解析——这反而带来运行时开销和依赖,通常不如直接用宏
__func__
更实际。

相关文章