std::source_location¶
| 标准 | 难度 | 状态 | 分级 | 证据 | 审阅 | 最后校验 |
|---|---|---|---|---|---|---|
| C++20 | 2/5 | final | A | 3 条(manual 3) | 单审(zyuhao7) | 2026-10-26 |
编译器支持:GCC 11+ · Clang 16+ · MSVC 19.29+。版本查询:cppreference 编译器支持。
主张与证据¶
| 主张 | 支持证据 |
|---|---|
| 为 std::source_location 提供格式化器,直接输出文件、行号、列号与函数名 | ev-fmt-source-location-formatter-001 |
| 在标准类型可用时把兼容类型别名到 std::source_location | ev-abseil-source-location-alias-002 |
| 日志消息重载接收 SourceLocation 并输出文件名与行号 | ev-abseil-source-location-stream-003 |
本站证据页:ev-abseil-source-location-alias-002 · ev-abseil-source-location-stream-003 · ev-fmt-source-location-formatter-001
解决什么问题¶
诊断信息需要知道“这次调用来自哪里”。传统写法是让调用方传 __FILE__、__LINE__,或者用宏把位置拼进函数参数。宏会污染调用点,参数列表也会变长;日志库往往还要维护一整套 LOG_INFO(...) 宏来捕获位置。
std::source_location 把文件名、行号、列号和函数名打包成一个轻量值。把它写成函数的默认实参,调用方不传任何东西,位置就自动记录在调用点。
基础用法¶
#include <iostream>
#include <source_location>
void log(const std::source_location& location =
std::source_location::current()) {
std::cout << location.file_name() << ':' << location.line() << ' '
<< location.function_name() << '\n';
}
int main() {
log();
}
std::source_location::current() 在默认实参中求值时,记录的是调用 log() 的位置,而不是 log 函数体内部的位置。file_name()、line()、column()、function_name() 都是只读查询,不需要额外分配。
进阶用法¶
默认实参只记录“调用发生的位置”。把捕获到的位置继续按值传给下层函数,可以保留原始调用点;在下层重新调用 current() 则会覆盖成下层自己的位置。
void trace(std::string_view tag,
const std::source_location &loc = std::source_location::current()) {
std::cout << tag << ' ' << loc.line() << '\n';
}
void wrapper(std::string_view tag,
const std::source_location &loc = std::source_location::current()) {
trace(tag, loc);
}
wrapper 的默认实参在调用点求值,随后把同一个 loc 传给 trace;因此 trace 输出的是 wrapper 调用者的行号,而不是 wrapper 函数体里的行号。包装日志接口时应传递位置对象,不要在包装层重新捕获。
完整示例见 examples/cpp20/source_location.cpp。
真实项目证据¶
| 项目 | 位置 | 用法 | 为什么典型 |
|---|---|---|---|
| fmt | include/fmt/std.h#L480-L494 |
为 std::source_location 特化格式化器 |
日志库需要在格式串里直接输出调用位置,格式化器让调用点保持简洁 |
| abseil-cpp | absl/types/source_location.h#L57-L65 |
标准类型可用时把 absl::SourceLocation 别名过去 |
兼容层避免维护两套位置类型,接口可以逐步迁移到标准类型 |
| abseil-cpp | absl/log/internal/log_message.h#L182-L187 |
日志消息重载接收位置对象并输出文件与行号 | 生产日志接口把位置类型作为一等参数,调用方不必展开位置宏 |
三条证据均为 manual 级:作者复核,尚未引入第二名审核者。三者都用特性测试宏保护标准类型;前两条覆盖格式化器与兼容别名,第三条展示日志接口如何消费位置对象。
陷阱与边界¶
默认实参才记录调用点。 在函数体里直接调用 std::source_location::current(),得到的是那一行的位置;要捕获调用方位置,必须把它放在默认实参或调用点的立即求值位置。
位置字符串不是稳定接口。 file_name() 可能是相对路径或绝对路径,column() 的实现和起始值由实现定义,function_name() 可能是空串或带签名的文本。不要把这些字符串用于逻辑判断或持久化。
它描述源码位置,不是调用栈。 source_location 只有一层调用点;需要完整调用链时用 std::stacktrace(C++23)或平台回溯设施。
没有统一的流输出。 std::source_location 不提供 operator<<;要直接打印,需要自定义格式化器、std::format 的 formatter,或手动拼接四个字段。
拷贝很便宜,但不要保存悬垂引用。 source_location 是值类型,按值或 const& 传递都可以;保存 file_name() 返回的 const char* 时,要确认它指向的字符串生命周期覆盖使用范围。
特性支持要检测。 使用 __cpp_lib_source_location 判断 <source_location> 是否可用;Clang 15 只有部分支持,16 才完整,不能只看 __cplusplus 的值。
库联动¶
- fmt:为
std::source_location特化格式化器,日志格式串可以直接输出文件、行号、列号与函数名。 - Abseil:
absl::SourceLocation在标准类型可用时别名到std::source_location,日志消息重载再消费位置对象并写出文件与行号。 - 与
std::stacktrace的分工:source_location只标记单个调用点;需要完整调用链时使用 C++23 的std::stacktrace,两者互补而不是替代。
横向对比¶
| 方案 | 调用方负担 | 类型安全 | 捕获列号/函数名 | 主要代价 |
|---|---|---|---|---|
__FILE__ / __LINE__ 宏 |
需要展开宏 | 否 | 否 | 宏污染、字符串拼接、难以封装 |
std::source_location |
无 | 是 | 是 | 需要 C++20;字符串内容实现定义 |
日志库的 LOG_* 宏 |
无 | 部分 | 取决于库 | 绑定日志框架,跨库传递位置需要适配 |
std::stacktrace |
无 | 是 | 是(调用栈) | C++23;捕获成本更高,符号化依赖平台 |
| 手动传入文件、行号参数 | 高 | 是 | 可选 | 每个调用点重复传参,容易传错 |
跨标准演进:C++20 引入 std::source_location;C++23 增加 std::stacktrace,两者互补:前者标记单个源码位置,后者记录调用链。