std::source_location是C++20标准库中用于编译期捕获调用点文件名、行号、函数名和列号的类型安全轻量结构体,需通过默认参数(如loc = std::source_location::current())在调用点自动注入位置信息,避免宏的类型不安全与位置偏差问题。

std::source_location 是什么,为什么日志里需要它
它不是宏,也不是编译器扩展,而是 C++20 标准库提供的一个轻量结构体,用来在编译期捕获调用点的文件名、函数名、行号和列号。传统日志宏靠 __FILE__、__LINE__ 等预定义宏拼接,既难维护又容易被宏展开干扰;而 std::source_location::current() 由编译器在调用处自动填充,语义清晰、类型安全,且能随函数内联保持准确位置。
关键点在于:它必须作为函数参数(通常是默认参数)传入,不能在宏里直接构造——否则所有日志都会指向宏定义处,而非调用处。
怎么写一个带 source_location 的日志函数
直接写一个可变参数函数,把 std::source_location 设为最后一个默认参数。这样调用时无需显式传参,编译器会自动填入当前上下文位置:
#include <source_location>
#include <iostream>
<p>void log(const char* msg, const std::source_location& loc = std::source_location::current()) {
std::cout << "[" << loc.file_name() << ":" << loc.line()
<< " in " << loc.function_name() << "] "
<< msg << "\n";
}</p>使用时就很简单:
立即学习“C++免费学习笔记(深入)”;
int main() {
log("user logged in"); // 输出类似:[main.cpp:12 in main] user logged in
}
- 必须用
const std::source_location&,避免拷贝开销(虽小但无必要) - 不能写成
std::source_location loc = {}或std::source_location::current()在函数体内——那会固定返回该函数定义处的位置 - 如果函数是 inline 或模板,
std::source_location::current()依然指向实际调用点,这是它的核心价值
宏封装时怎么避免 location 错位
很多人想用宏“简化调用”,但一不小心就把 std::source_location::current() 写在宏里,结果所有日志都显示宏定义所在的文件和行号。正确做法是:宏只做参数转发,把构造动作留给底层函数。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
错误写法(location 指向宏定义处):
#define LOG_BAD(msg) log(msg, std::source_location::current())
正确写法(location 指向宏调用处):
#define LOG(msg) log(msg)
或者更通用些,支持格式化:
#define LOGF(fmt, ...) log(fmt, ##__VA_ARGS__) // 配合重载或 fmt 库函数,但 source_location 仍由 log 函数接收
- 宏里不要出现
std::source_location::current() - 如果必须用宏(比如要统一加线程 ID、时间戳),也只做参数收集和转发,把
std::source_location::current()留给被调函数处理 - Clang/GCC/MSVC 对
std::source_location::current()的实现已稳定,但低于 C++20 标准的编译模式(如-std=c++17)会编译失败,需确认构建配置
和旧式 __FILE__/__LINE__ 宏比,有什么实际差异
表面上都是打日志位置,但行为差异很实在:
-
__FILE__是字符串字面量,loc.file_name()返回const char*,但内容可能被编译器优化裁剪(如只保留 basename);可用loc.file_name() + std::string_view::size()判断是否完整路径 -
__LINE__是整型常量,loc.line()也是unsigned int,但注意:它返回的是调用点行号,不是宏展开后生成代码的行号——这点比宏可靠得多 - 函数名:
__func__只有短名(如"foo"),loc.function_name()在 GCC/Clang 下返回带签名的全名(如"void bar(int)"),MSVC 目前只返回短名 - 性能上几乎无差别,
std::source_location是纯数据结构,无运行时开销
真正容易被忽略的是:它无法跨动态链接边界可靠工作——如果日志函数在 DLL/so 中,而调用来自外部模块,std::source_location::current() 仍反映调用点,但部分平台(尤其是 Windows + MSVC)对 function_name() 的支持可能退化。这种场景下,老老实实用 __FILE__ 和 __LINE__ 反而更稳。

















