
std::source_location 是什么,为什么能自动记录行号
std::source_location 是 C++20 引入的轻量结构体,用于在编译期捕获调用点的源码位置信息。它不依赖运行时堆栈遍历,也不触发函数内联抑制(只要编译器支持 C++20 且未禁用),因此开销极低。关键在于:它不是“获取当前行号”,而是“让调用者把它的位置传进来”——这决定了你必须在日志宏里用 __LINE__ 或更规范的 std::source_location::current() 显式捕获。
如何正确声明日志函数并接收 source_location
直接把 std::source_location 当作函数参数传入即可,但注意默认值必须是编译期可求值的表达式。最常用写法是用静态成员函数 std::source_location::current() 作为默认实参:
void log_debug(const char* msg, const std::source_location loc = std::source_location::current()) {
std::cout << "[" << loc.file_name() << ":" << loc.line() << "] " << msg << "\n";
}调用时无需手动传参:log_debug("value is 42"); 就会自动记录调用点的文件名与行号。若手动传入(比如封装中间层),需确保调用点仍是最终用户代码位置,否则行号会偏移。
- 不要在包装函数里直接写
std::source_location::current()—— 那会记录包装函数自身的行号 - 若需支持多参数日志(如格式化字符串),建议用宏包裹,再转发给实际函数
-
loc.function_name()在 GCC/Clang 中通常返回空字符串;MSVC 支持较好,但不可依赖
用宏封装避免手动传参和行号错位
纯函数调用无法解决“用户调用点 vs 包装函数”的行号偏移问题,必须用宏。宏展开发生在预处理阶段,std::source_location::current() 在宏体内求值,就落在用户代码行上:
立即学习“C++免费学习笔记(深入)”;
#define LOG_DEBUG(msg) \
log_debug(msg, std::source_location::current())这样 LOG_DEBUG("x = " + std::to_string(x)); 记录的就是这行本身的行号。更进一步,可支持格式化:
#define LOG_INFO(fmt, ...) \
do { \
std::ostringstream _oss; \
_oss << fmt; \
log_info(_oss.str().c_str(), std::source_location::current()); \
} while(0)注意:__VA_ARGS__ 展开后不能直接传给 std::source_location::current(),因为逗号会破坏宏参数解析;必须先拼接好字符串再传。
- 宏名全大写是惯例,避免与变量/函数名冲突
- 使用
do { ... } while(0)包裹可防止 if-else 分支中出现语法错误 - GCC/Clang 默认启用
-std=c++20后才支持std::source_location;MSVC 需 VS 2019 16.8+
常见错误:行号总是固定在某一行或报编译错误
典型现象包括:所有日志都显示同一行号(比如总显示在 log.h 第 42 行),或编译报错 error: 'std::source_location::current' is not a constant expression。
- 忘记加
-std=c++20或项目设置未启用 C++20 —— 这是最常见的原因 - 在模板函数或 constexpr 上下文中误用
std::source_location::current()—— 它不是 constexpr 函数,不能用于常量表达式 - 把
std::source_location::current()写在函数体内部而非默认参数位置,又没用宏 —— 行号永远是该函数定义处的行 - 使用了旧版标准库(如 libstdc++ __cpp_lib_source_location 宏是否已定义
验证是否生效,可在不同文件、不同行各调用一次 LOG_DEBUG("test"),观察输出的 loc.line() 是否随调用位置变化。不变化,说明捕获点没落在用户代码上。










