std::source_location 是 C++20 标准的类型安全源位置获取方案,需用 ::current() 显式调用获取调用点信息,不可默认构造或在包装函数内转发;相比 __FILE__/__LINE__ 宏,它支持传参、模板、constexpr,且路径行为稳定、无预处理缺陷。

用 std::source_location 直接取文件名和行号
在 C++20 中,std::source_location 是标准方案,不用宏、不依赖编译器扩展,类型安全且可传递。它本质是个轻量结构体,构造时自动捕获调用点的源码位置。
常见错误是手动传参或试图默认构造:std::source_location::current() 必须显式调用,且必须在目标函数内调用(不能在包装函数里偷懒转发)。
- 必须写成
std::source_location loc = std::source_location::current();,不能写std::source_location loc;(后者是默认构造,字段全为 0 或空字符串) - 如果封装成日志函数,要确保
std::source_location::current()出现在最外层调用处,比如log("msg", std::source_location::current()),而不是在log函数体内调用 -
loc.file_name()返回const char*,内容生命周期绑定到编译单元,可直接用,但别存指针长期引用(虽然通常没问题,但标准不保证)
为什么不用 __FILE__ 和 __LINE__ 宏
宏能用,但和 std::source_location 不是同一层次的东西:宏是预处理期文本替换,无类型、不可传递、无法参与模板推导;而 source_location 是运行时值,支持拷贝、传参、存储、甚至序列化(只要字段可访问)。
典型翻车场景是想把位置信息塞进类成员或 std::format:
立即学习“C++免费学习笔记(深入)”;
- 写
std::format("at {}:{}\n", __FILE__, __LINE__)看似可行,但__FILE__可能带绝对路径,长度爆炸,且无法控制编码(Windows 上常是窄字符 GBK) - 宏无法作为模板参数,也不能用于
constexpr上下文做编译期分支(而source_location::current()在函数内是constexpr友好的) - 某些构建系统或 IDE 会重写
__FILE__(比如用相对路径替换),导致日志里路径不一致;source_location::file_name()行为由标准规定,更稳定
source_location 的兼容性与性能开销
它不是零成本抽象,但开销极小:只读取当前栈帧的调试信息(实际是编译器在调用点埋的常量数据),没有函数调用、无内存分配、不触发任何运行时逻辑。
- Clang 12+、GCC 11+、MSVC 19.29+ 支持;低于这些版本会编译失败,别指望降级 fallback —— 没有标准替代品
- 启用
-g或/Zi并不影响source_location工作,它不依赖调试符号,而是编译时硬编码字面量 - 注意:如果函数被内联(尤其是
inline或[[always_inline]]),source_location::current()捕获的是调用点位置,不是函数定义处 —— 这正是你想要的行为
一个安全的日志封装示例
直接贴可用代码,重点看调用方式和参数位置:
#include <source_location>
#include <iostream>
#include <string_view>
<p>void log(std::string_view msg,
const std::source_location loc = std::source_location::current()) {
std::cerr << loc.file_name() << ":" << loc.line()
<< " - " << msg << "\n";
}</p><p>// 正确用法:位置信息自动来自 caller
int main() {
log("starting up"); // 输出类似:main.cpp:12 - starting up
return 0;
}</p>容易忽略的一点:std::source_location 默认参数只能用于非模板函数或明确指定模板参数的函数;如果写成函数模板,需额外约束或显式传参,否则可能因 SFINAE 失败或推导歧义报错。


















